手写RateLimiter

自定义注解 封装

如果需要让接口实现限流RateLimiter使用

网关:一般拦截所有的接口 实现限流 秒杀 抢购 或者大流量的接口才会实现限流。灵活

不是所有接口都需要限流  秒杀等接口需要限流

设计: 加注解的才可以实现限流 

注解形式而不是网关形式 只有需要限流的才加这个注解

 

传统的方式整合RateLimiter有很大缺点:代码重复量特别大,而且本身不支持注解方式

限流代码可以写在网关,相当于针对所有接口实现限流,维护性不强

不是所有的接口都需要限流 一般限流主要针对大流量,比如秒杀抢购 

 

分析案例:

 定义一个自定义注解

 Spring Boot整合 spring aop

 

使用环绕通知

   判断请求方法上是否有 注解

   如果有  使用反射获取注解方法上的参数

   调用原生RateLImiter方法创建令牌

   如果获取令牌超时  直接调用服务降级(自己定义)

   如果能够获取令牌 直接进入实际请求方法

 

本案例没有用到 扫包   直接请求过来走方法的

 

 

首先自定义注解:

 

 

引入maven依赖:

<!-- springboot 整合AOP -->
	         <dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>pom 

pom:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.toov5</groupId>
  <artifactId>springboot-guava</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.0.RELEASE</version>
	</parent>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>25.1-jre</version>
		</dependency>
		<!-- springboot 整合AOP -->
	   <dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
	</dependencies>
</project>

  

封装注解:

package com.toov5.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExtRateLimiter {
    //以秒为单位 固定的速录往桶中添加
   double permitsPerSecond();
   
   //在规定的时间内,如果没有获取到令牌的话,直接走降级处理
   long timeout();
}

aop封装:

package com.toov5.aop;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.google.common.util.concurrent.RateLimiter;
import com.toov5.annotation.ExtRateLimiter;

//aop环绕通知 判断拦截所有springmvc请求,判断请求方法上是否存在ExtRateLimiter

@Aspect  //aop两种方式 注解 和 xml方式
@Component
public class RateLimiterAop {
    // 存放接口是否已经存在
        private static ConcurrentHashMap<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<String, RateLimiter>();

     //定义切入点 拦截
        @Pointcut("execution(public * com.toov5.controller.*.*(..))")  //所有类 所有方法 任意参数
        public void rlAop() {
        }
        
        //使用aop环绕通知判断拦截所有springmvc请求,判断方法上是否存在ExRanteLimiter
        @Around("rlAop()")
        public Object doBefore(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            //1、判断请求方法上是否存在@RxtRateLimiter注解
            //2、如果请求方法上存在此注解@RxtRateLimiter注解 比如加上了RequestMapping表示请求方法
         Method sinatureMethod = getSinatureMethod(proceedingJoinPoint);
         if (sinatureMethod == null) {
            //直接报错  
             return null;
        }    
        
            //3、使用Java的反射机制获取拦截方法上自定义注解的参数
        ExtRateLimiter extRateLimiter = sinatureMethod.getDeclaredAnnotation(ExtRateLimiter.class);
        if (extRateLimiter==null) {  //方法上没有注解
            //直接放行代码 进入实际请求方法中
            proceedingJoinPoint.proceed();
        }
            //4、调用原生RateLimiter创建令牌
            double permitsPerSecond = extRateLimiter.permitsPerSecond();  //获取参数
            long timeout = extRateLimiter.timeout();
            //调用原生的RateLimiter创建令牌 保证每个请求对应的是单例的RateLimiter 一个请求一个RateLimiter 使用hashMap
            RateLimiter.create(permitsPerSecond);
            String requestURI = getRequestUrl();
            RateLimiter rateLimiter = null;
            if (rateLimiterMap.containsKey(requestURI)) {
                //如果检测到 
              rateLimiter = rateLimiterMap.get(requestURI);
            }else {
                //如果没有 则添加
                rateLimiter = RateLimiter.create(permitsPerSecond);
                rateLimiterMap.put(requestURI, rateLimiter);
            }
         
            //5、获取桶中的令牌,如果没有有效期获取到令牌,直接调用降级方法。
            boolean tryAcquire = rateLimiter.tryAcquire(timeout,TimeUnit.MILLISECONDS);
            if (!tryAcquire) {
                //服务降级
                fallback();
                return null;
            }
            //6、否则 直接进入到实际请求方法中
            
            return proceedingJoinPoint.proceed();
        } 
        private void fallback() throws IOException {
            //在aop编程中获取响应
             ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
             HttpServletResponse response = attributes.getResponse();
             //防止乱码
             response.setHeader("Content-type", "text/html;charset=UTF-8");
             PrintWriter writer = response.getWriter();
             try {
                 writer.println("亲,别抢了");
            } catch (Exception e) {
                writer.close();
            }
            
        }
     private String getRequestUrl() {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            return request.getRequestURI();
     }    
     private Method getSinatureMethod(ProceedingJoinPoint proceedingJoinPoint) {
         //获取到目标代理对象
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            //获取当前aop拦截的方法        
         Method method = signature.getMethod();
         return method;
     }
}

 

controller

package com.toov5.controller;

import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.google.common.util.concurrent.RateLimiter;
import com.toov5.annotation.ExtRateLimiter;
import com.toov5.service.OrderService;

@RestController
public class IndexController {
  @Autowired
  private OrderService orderService;
 //create方法中传入一个参数 以秒为单位固定的速率值 1r/s 往桶中存入一个令牌
  RateLimiter rateLimiter = RateLimiter.create(1);  //独立线程!它自己是个线程
   
  //相当于接口每秒只能接受一个客户端请求
  @RequestMapping("/addOrder")
    public String addOrder() {
        //限流放在网关 获取到当前 客户端从桶中获取对应的令牌 结果表示从桶中拿到令牌等待时间
        //如果获取不到令牌 就一直等待
        double acquire = rateLimiter.acquire();
        System.out.println("从桶中获取令牌等待时间"+acquire);
      boolean   tryAcquire=rateLimiter.tryAcquire(500,TimeUnit.MILLISECONDS);  //如果在500sms没有获取到令牌 直接走降级
      if (!tryAcquire) {
        System.out.println("别抢了,等等吧!");
        return "别抢了,等等吧!";
    }
        //业务逻辑处理
        boolean addOrderResult = orderService.addOrder();
        if (addOrderResult) {
            System.out.println("恭喜抢购成功!等待时间");
            return "恭喜抢购成功!";
        }
        
        return "抢购失败!";
    }
  //以每秒1个的速度往桶中添加令牌 
  @RequestMapping("/findIndex")
  @ExtRateLimiter(permitsPerSecond=1.0,timeout=500)
  public void findIndex() {
      System.out.println("findIndex"+System.currentTimeMillis());
  }
  
}

service

package com.toov5.service;

import org.springframework.stereotype.Service;

@Service
public class OrderService {
    
    public boolean addOrder() {
      System.out.println("db...正在操作订单表数据库");    
      return true;
    }
}

启动类

package com.toov5;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
      
}
}

 疯狂点击:

 

 

posted @ 2018-11-20 23:49  toov5  阅读(386)  评论(0编辑  收藏  举报