一.前言

  对于一个应用系统来说,我们有时会遇到极限并发的情况,即有一个TPS/QPS阀值,如果超了阀值可能会导致服务器崩溃宕机,因此我们最好进行过载保护,防止大量请求涌入击垮系统。对服务接口进行限流可以达到保护系统的效果,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理。

二.常见限流方案

  1.计数器法

    原理:在单位时间段内,对请求数进行计数,如果数量超过了单位时间的限制,则执行限流策略,当单位时间结束后,计数器清零,这个过程周而复始,就是计数器法。

    缺点:不能均衡限流,在一个单位时间的末尾和下一个单位时间的开始,很可能会有两个访问的峰值,导致系统崩溃。

    改进方式:可以通过减小单位时间来提高精度。

  2.漏桶算法

    原理:假设有一个水桶,水桶有一定的容量,所有请求不论速度都会注入到水桶中,然后水桶以一个恒定的速度向外将请求放出,当水桶满了的时候,新的请求被丢弃。

    优点:可以平滑请求,削减峰值。

    缺点:瓶颈会在漏出的速度,可能会拖慢整个系统,且不能有效地利用系统的资源。

    

  3.令牌桶算法(推荐)

    原理:有一个令牌桶,单位时间内令牌会以恒定的数量(即令牌的加入速度)加入到令牌桶中,所有请求都需要获取令牌才可正常访问。当令牌桶中没有令牌可取的时候,则拒绝请求。

    优点:相比漏桶算法,令牌桶算法允许一定的突发流量,但是又不会让突发流量超过我们给定的限制(单位时间窗口内的令牌数)。即限制了我们所说的 QPS(每秒查询率)。

    

  

  漏桶算法VS令牌桶算法 

  • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;
  • 漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;
  • 令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌),并允许一定程度突发流量;
  • 漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),从而平滑突发流入速率;
  • 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流入速率;
  • 两个算法实现可以一样,但是方向是相反的,对于相同的参数得到的限流效果是一样的。

三.Guava RateLimiter实现平滑限流

  Google开源工具包Guava提供了限流工具类RateLimiter,基于令牌桶算法实现。

  常用方法:

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

四.SpringBoot + Interceptor + 自定义注解应用

  1.maven依赖

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.1-jre</version>
</dependency>

  2.自定义注解

 1 import java.lang.annotation.*;
2 import java.util.concurrent.TimeUnit;
3
4 /**
5 * RequestLimiter 自定义注解接口限流
6 *
7 * @author xhq
8 * @version 1.0
9 * @date 2019/10/22 16:49
10 */
11 @Target({ElementType.METHOD})
12 @Retention(RetentionPolicy.RUNTIME)
13 @Documented
14 public @interface RequestLimiter {
15
16 /**
17 * 每秒创建令牌个数,默认:10
18 */
19 double QPS() default 10D;
20
21 /**
22 * 获取令牌等待超时时间 默认:500
23 */
24 long timeout() default 500;
25
26 /**
27 * 超时时间单位 默认:毫秒
28 */
29 TimeUnit timeunit() default TimeUnit.MILLISECONDS;
30
31 /**
32 * 无法获取令牌返回提示信息
33 */
34 String msg() default "亲,服务器快被挤爆了,请稍后再试!";
35 }

  3.拦截器

 1 import com.google.common.util.concurrent.RateLimiter;
2 import com.mowanka.framework.annotation.RequestLimiter;
3 import com.mowanka.framework.web.result.GenericResult;
4 import com.mowanka.framework.web.result.StateCode;
5 import org.springframework.stereotype.Component;
6 import org.springframework.web.method.HandlerMethod;
7
8 import javax.servlet.http.HttpServletRequest;
9 import javax.servlet.http.HttpServletResponse;
10 import java.util.Map;
11 import java.util.concurrent.ConcurrentHashMap;
12
13 /**
14 * 请求限流拦截器
15 *
16 * @author xhq
17 * @version 1.0
18 * @date 2019/10/22 16:46
19 */
20 @Component
21 public class RequestLimiterInterceptor extends GenericInterceptor {
22
23 /**
24 * 不同的方法存放不同的令牌桶
25 */
26 private final Map<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();
27
28 @Override
29 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
30 try {
31 if (handler instanceof HandlerMethod) {
32 HandlerMethod handlerMethod = (HandlerMethod) handler;
33 RequestLimiter rateLimit = handlerMethod.getMethodAnnotation(RequestLimiter.class);
34 //判断是否有注解
35 if (rateLimit != null) {
36 // 获取请求url
37 String url = request.getRequestURI();
38 RateLimiter rateLimiter;
39 // 判断map集合中是否有创建好的令牌桶
40 if (!rateLimiterMap.containsKey(url)) {
41 // 创建令牌桶,以n r/s往桶中放入令牌
42 rateLimiter = RateLimiter.create(rateLimit.QPS());
43 rateLimiterMap.put(url, rateLimiter);
44 }
45 rateLimiter = rateLimiterMap.get(url);
46 // 获取令牌
47 boolean acquire = rateLimiter.tryAcquire(rateLimit.timeout(), rateLimit.timeunit());
48 if (acquire) {
49 //获取令牌成功
50 return super.preHandle(request, response, handler);
51 } else {
52 log.warn("请求被限流,url:{}", request.getServletPath());
53 this.write(response, new GenericResult(StateCode.ERROR_SERVER, rateLimit.msg()));
54 return false;
55 }
56 }
57 }
58 return true;
59 } catch (Exception var6) {
60 var6.printStackTrace();
61 this.write(response, new GenericResult(StateCode.ERROR, "对不起,请求似乎出现了一些问题,请您稍后重试!"));
62 return false;
63 }
64 }
65
66 }

  4.注册拦截器

 1 /**
2 * springboot - WebMvcConfig
3 *
4 * @author xhq
5 * @version 1.0
6 */
7 @Configuration
8 public class WebMvcConfig implements WebMvcConfigurer {
9
10 /**
11 * 请求限流拦截器
12 */
13 @Autowired
14 protected RequestLimiterInterceptor requestLimiterInterceptor;
15
16 public WebMvcConfig() {}
17
18 @Override
19 public void addInterceptors(InterceptorRegistry registry) {
20 // 请求限流
21 registry.addInterceptor(requestLimiterInterceptor).addPathPatterns("/**");
22 }
23
24 }

  5.在接口上配置注解

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

五.总结

  1.该代码只适于单个应用进行接口限流,如果是分布式项目或者微服务项目可以采用nosql中央缓存(eg:redis)来实现。

  2.除了拦截器,当然也可以用filter和aop来实现。

  

Guava-RateLimiter实现令牌桶控制接口限流方案的更多相关文章

  1. 使用Guava的RateLimiter完成简单的大流量限流

    限流的一般思路: 1.随机丢弃一定规则的用户(迅速过滤掉90%的用户): 2.MQ削峰(比如设一个MQ可以容纳的最大消息量,达到这个量后MQ给予reject): 3.业务逻辑层使用RateLimite ...

  2. Guava的RateLimiter实现接口限流

    最近开发需求中有需要对后台接口进行限流处理,整理了一下基本使用方法. 首先添加guava依赖: <dependency> <groupId>com.google.guava&l ...

  3. 服务限流 -- 自定义注解基于RateLimiter实现接口限流

    1. 令牌桶限流算法 令牌桶会以一个恒定的速率向固定容量大小桶中放入令牌,当有浏览来时取走一个或者多个令牌,当发生高并发情况下拿到令牌的执行业务逻辑,没有获取到令牌的就会丢弃获取服务降级处理,提示一个 ...

  4. SpringCloud(8)---zuul权限校验、接口限流

    zuul权限校验.接口限流 一.权限校验搭建 正常项目开发时,权限校验可以考虑JWT和springSecurity结合进行权限校验,这个后期会总结,这里做个基于ZuulFilter过滤器进行一个简单的 ...

  5. 高并发之API接口限流

    在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流 缓存 缓存的目的是提升系统访问速度和增大系统处理容量 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再 ...

  6. Spring Cloud(7):Zuul自定义过滤器和接口限流

    上文讲到了Zuul的基本使用: https://www.cnblogs.com/xuyiqing/p/10884860.html 自定义Zuul过滤器: package org.dreamtech.a ...

  7. 【高并发】亿级流量场景下如何为HTTP接口限流?看完我懂了!!

    写在前面 在互联网应用中,高并发系统会面临一个重大的挑战,那就是大量流高并发访问,比如:天猫的双十一.京东618.秒杀.抢购促销等,这些都是典型的大流量高并发场景.关于秒杀,小伙伴们可以参见我的另一篇 ...

  8. SpringCloud之Zuul高并发情况下接口限流(十二)

    高并发下接口限流技术gauva(谷歌的框架) MySql最大连接数3000: 原理:框架每秒向桶里放100个令牌,接口请求来了先去拿令牌,拿到令牌后才能继续向后走,否则不允许向后执行:当接口请求太频繁 ...

  9. 使用google的guova开发高并发下的接口限流

    使用google的guova开发高并发下的接口限流 使用google的guova进行限流 1.guova的限流方式,在定时产生定量的令牌,令牌的数量限制了流量 2.增加一个订单接口限流类OrderRa ...

随机推荐

  1. JVM之堆参数

    1.Java 7和Java 8区别 Java 7堆结构 JDK 1.8之后将最初的永久代取消了,由元空间取代. 在Java8中,永久代已经被移除,被一个称为元空间的区域所取代.元空间的本质和永久代类似 ...

  2. fzu2202 犯罪嫌疑人

    Problem Description 福尔摩斯是个大侦探,他总是在解决疑难案件.这一次的案件也不例外,案件是这样的:有编号为1到N的N位嫌疑犯,他们其中有一个犯了罪,然后每个嫌疑犯都被询问,&quo ...

  3. Bone Collector II HDU - 2639 01背包第k最大值

    题意: 01背包,找出第k最优解 题解: 对于01背包最优解我们肯定都很熟悉 第k最优解的话也就是在dp方程上加一个维度来存它的第k最优解(dp[i][j]代表,体积为i能获得的第j最大价值) 对于每 ...

  4. 基于CentOS-7的redis下载和安装

    1.下载和安装 在我安装的虚拟机中,我把所有自己安装的软件都放在了/ph/install 目录下,具体以自己实际情况为准. [root@localhost ~]$ cd /ph/install #进入 ...

  5. python 表达式

    运算符 参考 https://www.runoob.com/python3/python3-basic-operators.html & https://www.runoob.com/pyth ...

  6. redis字典

    字典作为一种保存键值对的数据结构,在redis中使用十分广泛,redis作为数据库本身底层就是通过字典实现的,对redis的增删改查实际上也是构建在字典之上. 一.字典的结构

  7. 考研最路径dijkstra和floyd

    先来讲个段子:为什么 Dijkstra 不能提出 floyd 算法?因为他的名字是 ijk 而不是 kij. get不到点没有关系.我们今天的任务是看懂这个笑话. dijkstra 的效率是n^2.处 ...

  8. 自己yy的中缀表达式转后缀表达式(未验证完全正确)

    目前自己测试的表达式都没有出过问题 思路是这样,先将后缀表达式的计算顺序搞出来..当完全缩出来一个数的时候,如果后面还有要计算的,我们就把它放到后缀表达式的后面 先算后面的..不断迭代.. #incl ...

  9. PicGo:搭建图床

    PicGo:搭建图床 PicGo 免费搭建个人图床工具PicGo: 支持Windows.MacOS 和 Linux 软件目前覆盖的图床有8个平台: SM.MS图床.腾讯云COS.GitHub图床.七牛 ...

  10. u-boot 移植 --->2、在u-boot新增SOC和板子

    本次主要是要新增一个samsung的芯片到u-boot中,网上查阅资料发现s5pc1xx是与手上的S5PV210的友善的Tiny版子寄存器兼容的比较多,所以就准备以他为基础增加一个我的板子的支持到u- ...