SpringBoot基於RateLimiter+AOP動態的為不同接口限流


1.首先接口限流算法:
      1.計數器方式(傳統計數器缺點:臨界問題 可能違背定義固定速率原則)

     2.令牌桶方式

    https://www.weibo.com/u/3932251136

      3.漏桶方式

     4.應用層限流(Nginx)

2.限流實現:
    2.1. RateLimiter是guava提供的基於令牌桶算法的實現類,可以非常簡單的完成限流特技,並且根據系統的實際情況來調整生成token的速率。

    2.2.導入相關依賴包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
 
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>
 
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
 
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>20.0</version>
</dependency>
  2.3.代碼實現不多說每一步都有注解

    2.3.1 定義注解

    @Inherited
    @Documented
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RateLimit {
        double limitNum() default 20;  //默認每秒放入桶中的token
    }
    2.3.2 封裝定義返回結果

    public class MyResult {
        private Integer status;
        private String msg;
        private List<Object> data;
 
        public MyResult(Integer status, String msg, List<Object> data) {
            this.status = status;
            this.msg = msg;
            this.data = data;
        }
 
        public static MyResult OK(String msg, List<Object> data) {
            return new MyResult(200, msg, data);
        }
 
        public static MyResult Error(Integer status, String msg) {
            return new MyResult(status, msg, null);
        }
2.3.3 aop實現

@Component
@Scope
@Aspect
public class RateLimitAspect {
    private Logger log = LoggerFactory.getLogger(this.getClass());
    //用來存放不同接口的RateLimiter(key為接口名稱,value為RateLimiter)
    private ConcurrentHashMap<String, RateLimiter> map = new ConcurrentHashMap<>();
 
    private static ObjectMapper objectMapper = new ObjectMapper();
 
    private RateLimiter rateLimiter;
 
    @Autowired
    private HttpServletResponse response;
 
    @Pointcut("@annotation(com.icat.retalimitaop.annotation.RateLimit)")
    public void serviceLimit() {
    }
 
    @Around("serviceLimit()")
    public Object around(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        Object obj = null;
        //獲取攔截的方法名
        Signature sig = joinPoint.getSignature();
        //獲取攔截的方法名
        MethodSignature msig = (MethodSignature) sig;
        //返回被織入增加處理目標對象
        Object target = joinPoint.getTarget();
        //為了獲取注解信息
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        //獲取注解信息
        RateLimit annotation = currentMethod.getAnnotation(RateLimit.class);
        double limitNum = annotation.limitNum(); //獲取注解每秒加入桶中的token
        String functionName = msig.getName(); // 注解所在方法名區分不同的限流策略
 
        //獲取rateLimiter
         if(map.containsKey(functionName)){
             rateLimiter = map.get(functionName);
         }else {
             map.put(functionName, RateLimiter.create(limitNum));
             rateLimiter = map.get(functionName);
         }
 
        try {
            if (rateLimiter.tryAcquire()) {
                //執行方法
                obj = joinPoint.proceed();
            } else {
                //拒絕了請求(服務降級)
                String result = objectMapper.writeValueAsString(MyResult.Error(500, "系統繁忙!"));
                log.info("拒絕了請求:" + result);
                outErrorResult(result);
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return obj;
    }
    //將結果返回
    public void outErrorResult(String result) {
        response.setContentType("application/json;charset=UTF-8");
        try (ServletOutputStream outputStream = response.getOutputStream()) {
            outputStream.write(result.getBytes("utf-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    static {
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }
 
}
3.測試限流
   2個接口設定沒秒限流5個和美妙限流10個 
    @RateLimit(limitNum = 5.0)
    public MyResult getResults() {
        log.info("調用了方法getResults");
        return MyResult.OK("調用了方法", null);
    }
 
    @RateLimit(limitNum = 10.0)
    public MyResult getResultTwo() {
        log.info("調用了方法getResultTwo");
        return MyResult.OK("調用了方法getResultTwo", null);
   }
使用Jmeter測試getResults接口 20個並發(設定每秒只能處理5個請求)

使用Jmeter測試getResultTwo接口 20個並發(設定每秒只能處理10個請求)

  結果會比設定的多一個(百度了很久沒找到原因 -  -! )


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM