一.前言

  对于一个应用系统来说,我们有时会遇到极限并发的情况,即有一个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. 通过模拟数据,使用js在前端实现模糊查询下拉框功能实例教程

    所谓模糊查询就是通过关键字在数据中匹配到包含关键字的数据,而得出的查询结果.本实例教程讲解在前端文本框输入关键字,显示匹配的数据列表功能. 首先得准备一个文本框和显示数据列表的div元素,html代码 ...

  2. Linux远程拷贝scp

    Linux的scp命令可以实现两台服务器之间互相拷贝文件,我的测试环境是Centos6.4. 基本的命令格式 scp 拷贝目标文件 远程用户@远程主机地址:远程目录 一.从本机拷贝到目标远程主机 # ...

  3. hdu 6268 Master of Subgraph(点分治+bitset)

    You are given a tree with n nodes. The weight of the i-th node is wi. Given a positive integer m, no ...

  4. HDU6321 Dynamic Graph Matching【状压DP 子集枚举】

    HDU6321 Dynamic Graph Matching 题意: 给出\(N\)个点,一开始没有边,然后有\(M\)次操作,每次操作加一条无向边或者删一条已经存在的边,问每次操作后图中恰好匹配\( ...

  5. Codeforces Round #660 (Div. 2) A. Captain Flint and Crew Recruitment、Captain Flint and a Long Voyage

    题目链接:Captain Flint and Crew Recruitment 题意: t组输入,每一组输入一个n.这里我们说一下题目定义的近似质数概念: "如果可以将正整数x表示为p⋅q, ...

  6. 1569: Wet Tiles

    Description Alice owns a construction company in the town of Norainia, famous for its unusually dry ...

  7. A. Little Elephant and Interval

    The Little Elephant very much loves sums on intervals. This time he has a pair of integers l and r ( ...

  8. Codeforces Round #672 (Div. 2) A. Cubes Sorting (思维)

    题意:有一长度为\(n\)的一组数,每次可以交换两个数的位置,问能否在\(\frac{n*(n-1)}{2}-1\)次操作内使得数组非递减. 题解:不难发现,只有当整个数组严格递减的时候,操作次数是\ ...

  9. Windows Terminal 更换主题

    1. 打开设置,是个json文件 2. 在此处获取主题配置:https://atomcorp.github.io/themes/ 3.将主题配置粘贴到schemes节点(可以增加N个) 4.配置每个命 ...

  10. Python进阶丨如何创建你的第一个Python元类?

    摘要:通过本文,将深入讨论Python元类,其属性,如何以及何时在Python中使用元类. Python元类设置类的行为和规则.元类有助于修改类的实例,并且相当复杂,是Python编程的高级功能之一. ...