Guava-RateLimiter实现令牌桶控制接口限流方案
一.前言
对于一个应用系统来说,我们有时会遇到极限并发的情况,即有一个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实现令牌桶控制接口限流方案的更多相关文章
- 使用Guava的RateLimiter完成简单的大流量限流
限流的一般思路: 1.随机丢弃一定规则的用户(迅速过滤掉90%的用户): 2.MQ削峰(比如设一个MQ可以容纳的最大消息量,达到这个量后MQ给予reject): 3.业务逻辑层使用RateLimite ...
- Guava的RateLimiter实现接口限流
最近开发需求中有需要对后台接口进行限流处理,整理了一下基本使用方法. 首先添加guava依赖: <dependency> <groupId>com.google.guava&l ...
- 服务限流 -- 自定义注解基于RateLimiter实现接口限流
1. 令牌桶限流算法 令牌桶会以一个恒定的速率向固定容量大小桶中放入令牌,当有浏览来时取走一个或者多个令牌,当发生高并发情况下拿到令牌的执行业务逻辑,没有获取到令牌的就会丢弃获取服务降级处理,提示一个 ...
- SpringCloud(8)---zuul权限校验、接口限流
zuul权限校验.接口限流 一.权限校验搭建 正常项目开发时,权限校验可以考虑JWT和springSecurity结合进行权限校验,这个后期会总结,这里做个基于ZuulFilter过滤器进行一个简单的 ...
- 高并发之API接口限流
在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流 缓存 缓存的目的是提升系统访问速度和增大系统处理容量 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再 ...
- Spring Cloud(7):Zuul自定义过滤器和接口限流
上文讲到了Zuul的基本使用: https://www.cnblogs.com/xuyiqing/p/10884860.html 自定义Zuul过滤器: package org.dreamtech.a ...
- 【高并发】亿级流量场景下如何为HTTP接口限流?看完我懂了!!
写在前面 在互联网应用中,高并发系统会面临一个重大的挑战,那就是大量流高并发访问,比如:天猫的双十一.京东618.秒杀.抢购促销等,这些都是典型的大流量高并发场景.关于秒杀,小伙伴们可以参见我的另一篇 ...
- SpringCloud之Zuul高并发情况下接口限流(十二)
高并发下接口限流技术gauva(谷歌的框架) MySql最大连接数3000: 原理:框架每秒向桶里放100个令牌,接口请求来了先去拿令牌,拿到令牌后才能继续向后走,否则不允许向后执行:当接口请求太频繁 ...
- 使用google的guova开发高并发下的接口限流
使用google的guova开发高并发下的接口限流 使用google的guova进行限流 1.guova的限流方式,在定时产生定量的令牌,令牌的数量限制了流量 2.增加一个订单接口限流类OrderRa ...
随机推荐
- idea--忽略隐藏文件、文件夹的设置操作
文章由来 公司同事在群里问了个问题,如下: 为了大家看清,将图特意贴出来: 这人还删除idae重装了下,哈哈,才到群里问的. 解决思路(按顺序) 1.我让他直接拉会,共享桌面我给看了下,首先是open ...
- yum.repos.d中的变量($releasever与$basearch)
今天打算更新一下centos的repo源,把原先国外的repo地址换成国内的,速度快一些.主要替换的文件是/etc/yum.repos.d/Centos-Base.repo .替换的时候,不知道大家有 ...
- vuex-pathify 一个基于vuex进行封装的 vuex助手语法插件
首先介绍一下此插件 我们的目标是什么:干死vuex 我来当皇上!(开个玩笑,pathify的是为了简化vuex的开发体验) 插件作者 davestewart github仓库地址 官方网站,英文 说一 ...
- Ubuntu18.04系统设置为中文语言
1.选择右上角设置按钮 2.管理已安装的语言 3.安装简体中文 安装好后是这样的 会发现汉语中文那一块是灰色的,怎么点都点不亮 4.拖拽 汉语(中国) 到最顶边 然后应用 5.重启 然后就出现这个画面 ...
- JVM之JVM体系结构
JVM是运行在操作系统之上的,它与硬件没有直接的交互 下图运行时数据区灰色代表线程私有,亮色(方法区和堆)代表所有线程共享. 1.类装载器ClassLoader 负责加载class文件,class文件 ...
- A - A Gifts Fixing
t组询问,每次给出数列长度n 以及两个长度为n的数列{ai}和{bi}. 有三种操作:ai−1, bi−1以及ai,bi同时− 1 -1−1. 问最少多少步以后可以让两个数列变成常数数列. ...
- 2019牛客暑期多校训练营(第一场)A Equivalent Prefixes
传送门 题意: 先输入一个n,代表两个数组里面都有n个数,然后让你从中找到一个p<=n,使其满足(1<=l<=r<=p<=n)可以让在(l,r)这个区间内在两个数组中的的 ...
- zjnu1181 石子合并【基础算法・动态规划】——高级
Description 在操场上沿一直线排列着 n堆石子.现要将石子有次序地合并成一堆.规定每次只能选相邻的两堆石子合并成新的一堆, 并将新的一堆石子数记为该次合并的得分.允许在第一次合并前对调一 ...
- javascript——function类型(this关键字)
如果不用分组的话,当用exec检测rar的时候会错误 结果: Function: 返回值为三(不推荐)
- 5.2 spring5源码--spring AOP源码分析三---切面源码分析
一. AOP切面源码分析 源码分析分为三部分 1. 解析切面 2. 创建动态代理 3. 调用 源码的入口 源码分析的入口, 从注解开始: 组件的入口是一个注解, 比如启用AOP的注解@EnableAs ...