一.前言

  对于一个应用系统来说,我们有时会遇到极限并发的情况,即有一个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. 第2层交换和生成树协议(STP)__MAC地址表

    1.MAC(Media Access Control, 介质访问控制)地址是识别LAN节点的标识.网卡的物理地址通常是由网卡生产厂家烧入网卡的EPROM(一种闪存芯片,通常可以通过程序擦写),它存储的 ...

  2. Educational Codeforces Round 85 (Rated for Div. 2)

    \(Educational\ Codeforces\ Round\ 85\ (Rated\ for\ Div.2)\) \(A. Level Statistics\) 每天都可能会有人玩游戏,同时一部 ...

  3. hdu5233 Gunner II

    Problem Description Long long ago, there was a gunner whose name is Jack. He likes to go hunting ver ...

  4. Educational DP Contest E - Knapsack 2 (01背包进阶版)

    题意:有\(n\)个物品,第\(i\)个物品价值\(v_{i}\),体积为\(w_{i}\),你有容量为\(W\)的背包,求能放物品的最大价值. 题解:经典01背包,但是物品的最大体积给到了\(10^ ...

  5. VSCode配置Python开发环境

    https://blog.csdn.net/vinkim/article/details/81546333 https://zhuanlan.zhihu.com/p/31417084

  6. EGADS介绍(二)--时序模型和异常检测模型算法的核心思想

    EDADS系统包含了众多的时序模型和异常检测模型,这些模型的处理会输入很多参数,若仅使用默认的参数,那么时序模型预测的准确率将无法提高,异常检测模型的误报率也无法降低,甚至针对某些时间序列这些模型将无 ...

  7. 多线程之ThreadLocal类

    深入研究java.lang.ThreadLocal类 0.前言 ThreadLocal(线程变量副本)Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量.采用空间换 ...

  8. 爬虫入门二 beautifulsoup

    title: 爬虫入门二 beautifulsoup date: 2020-03-12 14:43:00 categories: python tags: crawler 使用beautifulsou ...

  9. PAT l2-010 排座位 【并查集】

    L2-010. 排座位 时间限制 150 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 布置宴席最微妙的事情,就是给前来参宴的各位宾客安排座位. ...

  10. codeforce 849A

    A. Odds and Ends time limit per test 1 second memory limit per test 256 megabytes input standard inp ...