Guava令牌桶实现全局限流-单体服务

简介

令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,
​ 对于一个应用系统来说,我们有时会遇到极限并发的情况,即有一个TPS/QPS阀值,如果超了阀值可能会导致服务器崩溃宕机,因此我们最好进行过载保护,防止大量请求涌入击垮系统。对服务接口进行限流可以达到保护系统的效果,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理。

​ 原理:有一个令牌桶,单位时间内令牌会以恒定的数量(即令牌的加入速度)加入到令牌桶中,所有请求都需要获取令牌才可正常访问。当令牌桶中没有令牌可取的时候,则拒绝请求。
优点:相比漏桶算法,令牌桶算法允许一定的突发流量,但是又不会让突发流量超过我们给定的限制(单位时间窗口内的令牌数)。即限制了我们所说的 QPS(每秒查询率)。

常用方法:
create(Double permitsPerSecond)方法根据给定的(令牌:单位时间(1s))比例为令牌生成速率。     
tryAcquire()方法尝试获取一个令牌,立即返回true/false,不阻塞,重载方法具备设置获取令牌个数、获取最大等待时间等参数。     
acquire()方法与tryAcquire类似,但是会阻塞,尝试获取一个令牌,没有时则阻塞直到获取成功。

使用

1、maven依赖

<!-- 使用Guava令桶-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>20.0</version>
        </dependency>

2、自定义注解


  /**
   * RequestLimiter 自定义注解接口限流
   *
   * @author 
   * @version 1.0
   * @date
  */
 @Target({ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface RequestLimiter {
 
     /**
      * 每秒创建令牌个数,默认:10
      */
     double QPS() default 10D;
 
     /**
      * 获取令牌等待超时时间 默认:500
      */
     long timeout() default 500;
 
     /**
      * 超时时间单位 默认:毫秒
      */
     TimeUnit timeunit() default TimeUnit.MILLISECONDS;
 
     /**
      * 无法获取令牌返回提示信息
      */
     String msg() default "亲,服务器快被挤爆了,请稍后再试!";
 }

3、自定义拦截器


 /**
  * 请求限流拦截器
  *
  * @author 
  * @version 1.0
  * @date
  */
 @Component
 public class RequestLimiterInterceptor extends GenericInterceptor {
 
     /**
      * 不同的方法存放不同的令牌桶
      */
     private final Map<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();
 
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
         try {
             if (handler instanceof HandlerMethod) {
                 HandlerMethod handlerMethod = (HandlerMethod) handler;
                 RequestLimiter rateLimit = handlerMethod.getMethodAnnotation(RequestLimiter.class);
                 //判断是否有注解
                 if (rateLimit != null) {
                     // 获取请求url
                     String url = request.getRequestURI();
                     RateLimiter rateLimiter;
                     // 判断map集合中是否有创建好的令牌桶
                     if (!rateLimiterMap.containsKey(url)) {
                         // 创建令牌桶,以n r/s往桶中放入令牌
                         rateLimiter = RateLimiter.create(rateLimit.QPS());
                         rateLimiterMap.put(url, rateLimiter);
                     }
                     rateLimiter = rateLimiterMap.get(url);
                     // 获取令牌
                     boolean acquire = rateLimiter.tryAcquire(rateLimit.timeout(), rateLimit.timeunit());
                     if (acquire) {
                         //获取令牌成功
                         return super.preHandle(request, response, handler);
                     } else {
                         log.warn("请求被限流,url:{}", request.getServletPath());
                         this.write(response, new GenericResult(StateCode.ERROR_SERVER, rateLimit.msg()));
                         return false;
                     }
                 }
             }
             return true;
         } catch (Exception var6) {
             var6.printStackTrace();
             this.write(response, new GenericResult(StateCode.ERROR, "对不起,请求似乎出现了一些问题,请您稍后重试!"));
             return false;
         }
     }
 
 }


 /**
  * 请求限流拦截器
  *
  * @author 
  * @version 1.0
  * @date
  */
 @Component
 public class RequestLimiterInterceptor extends GenericInterceptor {
 
     /**
      * 不同的方法存放不同的令牌桶
      */
     private final Map<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();
 
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
         try {
             if (handler instanceof HandlerMethod) {
                 HandlerMethod handlerMethod = (HandlerMethod) handler;
                 RequestLimiter rateLimit = handlerMethod.getMethodAnnotation(RequestLimiter.class);
                 //判断是否有注解
                 if (rateLimit != null) {
                     // 获取请求url
                     String url = request.getRequestURI();
                     RateLimiter rateLimiter;
                     // 判断map集合中是否有创建好的令牌桶
                     if (!rateLimiterMap.containsKey(url)) {
                         // 创建令牌桶,以n r/s往桶中放入令牌
                         rateLimiter = RateLimiter.create(rateLimit.QPS());
                         rateLimiterMap.put(url, rateLimiter);
                     }
                     rateLimiter = rateLimiterMap.get(url);
                     // 获取令牌
                     boolean acquire = rateLimiter.tryAcquire(rateLimit.timeout(), rateLimit.timeunit());
                     if (acquire) {
                         //获取令牌成功
                         return super.preHandle(request, response, handler);
                     } else {
                         log.warn("请求被限流,url:{}", request.getServletPath());
                         this.write(response, new GenericResult(StateCode.ERROR_SERVER, rateLimit.msg()));
                         return false;
                     }
                 }
             }
             return true;
         } catch (Exception var6) {
             var6.printStackTrace();
             this.write(response, new GenericResult(StateCode.ERROR, "对不起,请求似乎出现了一些问题,请您稍后重试!"));
             return false;
         }
     }
 
 }

4、注册拦截器

@Configuration
@EnableWebMvc
@Slf4j
 public class WebMvcConfig implements WebMvcConfigurer {
 
     /**
      * 请求限流拦截器
      */
     @Autowired
     protected RequestLimiterInterceptor requestLimiterInterceptor;
 
     public WebMvcConfig() {}
 
    /**
     * 向Web中添加拦截器
     * @param registration
     */
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
         // 请求限流  配置拦截器,拦截所有以/ 请求
         registry.addInterceptor(requestLimiterInterceptor).addPathPatterns("/**");
     }
 
 }

5、在接口上使用拦截器

@RequestLimiter(QPS = 5D, timeout = 200, timeunit = TimeUnit.MILLISECONDS,msg = "服务器繁忙,请稍后再试")
@GetMapping("/test")
@ResponseBody
public String test(){
      return "";
}

你可能感兴趣的:(java)