限流-RateLimiter

工作中对外提供的API 接口设计都要考虑限流,如果不考虑限流,会成系统的连锁反应,轻者响应缓慢,重者系统宕机,整个业务线崩溃,如何应对这种情况呢,我们可以对请求进行引流或者直接拒绝等操作,保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机。
在开发高并发系统时有三把利器用来保护系统:

  • 缓存的目的是提升系统访问速度和增大系统处理容量
  • 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开
  • 限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以对请求进行处理:拒绝、排队、等待、降级

常用的限流算法

漏桶算法

把请求比作是水,水来了都先放进桶里,并以限定的速度出水,当水来得过猛而出水不够快时就会导致水直接溢出,即拒绝服务。

漏斗有一个进水口和一个出水口,出水口以一定速率出水,并且有一个最大出水速率。
在漏斗中没有水的时候:

  • 如果进水速率小于等于最大出水速率,那么,出水速率等于进水速率,此时,不会积水
  • 如果进水速率大于最大出水速率,那么,漏斗以最大速率出水,此时,多余的水会积在漏斗中

在漏斗中有水的时候:

  • 出水口以最大速率出水
  • 如果漏斗未满,且有进水的话,那么这些水会积在漏斗中
  • 如果漏斗已满,且有进水的话,那么这些水会溢出到漏斗之外

令牌桶算法


对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。
令牌桶算法的原理是系统以恒定的速率产生令牌,然后把令牌放到令牌桶中,令牌桶有一个容量,当令牌桶满了的时候,再向其中放令牌,那么多余的令牌会被丢弃;当想要处理一个请求的时候,需要从令牌桶中取出一个令牌,如果此时令牌桶中没有令牌,那么则拒绝该请求。

RateLimiter简介

Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法(Token Bucket)来完成限流。RateLimiter经常用于限制对一些物理资源或者逻辑资源的访问速率。它支持两种获取接口,一种是如果拿不到立刻返回false,一种会阻塞等待一段时间看能不能拿到。

@Test
fun rateLimiterTest() {val rateLimiter = RateLimiter.create(0.5)arrayOf(1,6,2).forEach {println("${System.currentTimeMillis()} acq $it:\twait ${rateLimiter.acquire(it)}s")}
}

以上示例,创建一个RateLimiter,指定每秒放0.5个令牌(2秒放1个令牌),其输出见下:

1516166482561 acq 1: wait 0.0s
1516166482563 acq 6: wait 1.997664s
1516166484569 acq 2: wait 11.991958s

从输出结果可以看出,RateLimiter具有预消费的能力:
acq 1时并没有任何等待直接预消费了1个令牌;
acq 6时,由于之前预消费了1个令牌,故而等待了2秒,之后又预消费了6个令牌;
acq 2时同理,由于之前预消费了6个令牌,故而等待了12秒;
RateLimiter通过限制后面请求的等待时间,来支持一定程度的突发请求(预消费)。

源码解读

Guava有两种限流模式,一种为稳定模式(SmoothBursty:令牌生成速度恒定),一种为渐进模式(SmoothWarmingUp:令牌生成速度缓慢提升直到维持在一个稳定值)两种模式实现思路类似,主要区别在等待时间的计算上,本篇重点介绍SmoothBursty。
先来看看SmoothBursty中几个属性的含义:

/*** The currently stored permits.* 当前存储令牌数*/
double storedPermits;/*** The maximum number of stored permits.* 最大存储令牌数*/
double maxPermits;/*** The interval between two unit requests, at our stable rate. E.g., a stable rate of 5 permits* per second has a stable interval of 200ms.* 添加令牌时间间隔*/
double stableIntervalMicros;/*** The time when the next request (no matter its size) will be granted. After granting a request,* this is pushed further in the future. Large requests push this further than small requests.* 下一次请求可以获取令牌的起始时间* 由于RateLimiter允许预消费,上次请求预消费令牌后* 下次请求需要等待相应的时间到nextFreeTicketMicros时刻才可以获取令牌*/
private long nextFreeTicketMicros = 0L; // could be either in the past or future

接下来介绍几个关键函数:

/*** Updates {@code storedPermits} and {@code nextFreeTicketMicros} based on the current time.*/
void resync(long nowMicros) {// if nextFreeTicket is in the past, resync to nowif (nowMicros > nextFreeTicketMicros) {double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();storedPermits = min(maxPermits, storedPermits + newPermits);nextFreeTicketMicros = nowMicros;}
}

根据令牌桶算法,桶中的令牌是持续生成存放的,有请求时需要先从桶中拿到令牌才能开始执行,谁来持续生成令牌存放呢?
一种解法是,开启一个定时任务,由定时任务持续生成令牌。这样的问题在于会极大的消耗系统资源,如,某接口需要分别对每个用户做访问频率限制,假设系统中存在6W用户,则至多需要开启6W个定时任务来维持每个桶中的令牌数,这样的开销是巨大的。
另一种解法则是 延迟计算 ,如下resync函数。该函数会在每次获取令牌之前调用,其实现思路为,若当前时间晚于nextFreeTicketMicros,则计算该段时间内可以生成多少令牌,将生成的令牌加入令牌桶中并更新数据。这样一来,只需要在获取令牌时计算一次即可。

final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {resync(nowMicros);long returnValue = nextFreeTicketMicros; // 返回的是上次计算的nextFreeTicketMicrosdouble storedPermitsToSpend = min(requiredPermits, this.storedPermits); // 可以消费的令牌数double freshPermits = requiredPermits - storedPermitsToSpend; // 还需要的令牌数long waitMicros =storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)+ (long) (freshPermits * stableIntervalMicros); // 根据freshPermits计算需要等待的时间this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros); // 本次计算的nextFreeTicketMicros不返回this.storedPermits -= storedPermitsToSpend;return returnValue;
}

该函数用于获取requiredPermits个令牌,并返回需要等待到的时间点
其中,storedPermitsToSpend为桶中可以消费的令牌数,freshPermits为还需要的(需要补充的)令牌数,根据该值计算需要等待的时间,追加并更新到nextFreeTicketMicros。
需要注意的是,该函数的返回是更新前的(上次请求计算的)nextFreeTicketMicros,而不是本次更新的nextFreeTicketMicros,通俗来讲,本次请求需要为上次请求的预消费行为埋单,这也是RateLimiter可以 预消费 (处理突发)的原理所在。若需要禁止预消费,则修改此处返回更新后的nextFreeTicketMicros值。

我们将通过一个例子来解释它是如何工作的:
对一个每秒产生一个令牌的RateLimiter,每有一个没有使用令牌的一秒,我们就将storedPermits加1,如果RateLimiter在10秒都没有使用,则storedPermits变成10.0.这个时候,一个请求到来并请求三个令牌(acquire(3)),我们将从storedPermits中的令牌为其服务,storedPermits变为7.0.这个请求之后立马又有一个请求到来并请求10个令牌,我们将从storedPermits剩余的7个令牌给这个请求,剩下还需要三个令牌,我们将从RateLimiter新产生的令牌中获取.我们已经知道,RateLimiter每秒新产生1个令牌,就是说上面这个请求还需要的3个请求就要求其等待3秒。
想象一个RateLimiter每秒产生一个令牌,现在完全没有使用(处于初始状态),限制一个昂贵的请求acquire(100)过来.如果我们选择让这个请求等待100秒再允许其执行,这显然很荒谬.我们为什么什么也不做而只是傻傻的等待100秒,一个更好的做法是允许这个请求立即执行(和acquire(1)没有区别),然后将随后到来的请求推迟到正确的时间点.这种策略,我们允许这个昂贵的任务立即执行,并将随后到来的请求推迟100秒.这种策略就是让任务的执行和等待同时进行.

一个重要的结论:RateLimiter不会记最后一个请求,而是记下一个请求允许执行的时间.这也可以很直白的告诉我们到达下一个调度时间点的时间间隔.然后定一个一段时间未使用的Ratelimiter也很简单:下一个调度时间点已经过去,这个时间点和现在时间的差就是Ratelimiter多久没有被使用,我们会将这一段时间翻译成storedPermits.所以,如果每秒钟产生一个令牌(rate==1),并且正好每秒来一个请求,那么storedPermits就不会增长.

限流-RateLimiter相关推荐

  1. guava之限流RateLimiter

    常用的限流方式和场景有: 限制总并发数(比如数据库连接池.线程池) 限制瞬时并发数(如nginx的limitconn模块,用来限制瞬时并发连接数,Java的Semaphore也可以实现) 限制时间窗口 ...

  2. 【java】高并发之限流 RateLimiter使用

    1.概述 转载原文:高并发之限流 你可能知道高并发系统需要限流这个东西,但具体是限制的什么,该如何去做,还是模凌两可.我们接下来系统性的给它归个小类,希望对你有所帮助. google guava中提供 ...

  3. Guava系列之限流RateLimiter

    在互联网高并发场景下,限流是用来保证系统稳定性的一种手段,当系统遭遇瞬时流量激增时,可能会由于系统资源耗尽导致宕机.而限流可以把一小部分流量拒绝掉,保证大部分流量可以正常访问,从而保证系统只接收承受范 ...

  4. 超详细的Guava RateLimiter限流原理解析

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 限流是保护高并发系统的三把利器之一,另外两个是缓存和降级.限流在很多场景中用来限制并发和请求量,比 ...

  5. Guava RateLimiter限流原理解析

    来源:https://zhuanlan.zhihu.com/p/60979444 限流是保护高并发系统的三把利器之一,另外两个是缓存和降级.限流在很多场景中用来限制并发和请求量,比如说秒杀抢购,保护自 ...

  6. 【Flink】Flink 消费 kafka 实现 限流处理 RateLimiter

    文章目录 1.概述 2.案例 2.1 案例1 纪念一波,九师兄博客热门订阅专栏时常名列前茅,我飘了,哈哈哈哈,得意的笑 1.概述 首先看看 [java]高并发之限流 RateLimiter使用 这个去 ...

  7. 什么是限流?你真的了解吗?

    之前在学习的时候也接触不到高并发/大流量这种东西,所以限流当然是没接触过的了.在看公司项目的时候,发现有用到限流(RateLimiter),顺带了解一波. 一.限流基础知识介绍 为啥要限流,相信就不用 ...

  8. Guava RateLimiter限流源码解析和实例应用

    2019独角兽企业重金招聘Python工程师标准>>> 在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流 缓存 缓存的目的是提升系统访问速度和增大系统处理容量 降级 降级是 ...

  9. 【Guava】使用Guava的RateLimiter做限流

    2019独角兽企业重金招聘Python工程师标准>>> 一.常见的限流算法 目前常用的限流算法有两个:漏桶算法和令牌桶算法. 1.漏桶算法 漏桶算法的原理比较简单,请求进入到漏桶中, ...

最新文章

  1. 2017敏捷沙滩大会:完美软件,测量持续交付,以及探索未来
  2. 利用人体肤色从图像中分割出人体区域的OpenCV代码
  3. 非洲的风能和太阳能真是企业家无与伦比的商机?
  4. 第五章:系统困境之 你的努力忽略了关键限制因素
  5. COM原理及应用之可连接对象
  6. postgresql 相关杂记
  7. 第四:SpringBoot生成Api管理mysql内保存的测试数据(接口自动化平台扩展)
  8. 修改阿里云ECS服务器的系统时区
  9. C# 未能加载文件或程序集“ Newtonsoft.Json” Json格式错误
  10. JS 在线预览Word
  11. Anaconda3+Tensorflow2.0(gpu)安装教程-小新Pro13英特尔独显版win10系统
  12. selenium:表单frame切换和句柄窗口切换
  13. docker镜像 私有创库、端口映射以及 数据卷的创建
  14. 作为应届大学生的我和准职业人的差距
  15. m3云服务器_“中国球迷”索尼A7RM3及镜头下的情况肖像
  16. Ubuntu16.04安装steam
  17. UML软件建模StarUML
  18. 你对计算机有什么看法英语作文,关于电脑优点英语作文
  19. 51单片机与AVR(SPI)单片机驱动DS1302
  20. 嘉立创电路板制作过程全流程详解(三):图电、AOI

热门文章

  1. 树莓派下载Ubuntu20.04.3版本 +通过设置找到wifi标志+开启vnc远程桌面+灰屏解决方法
  2. chroot运行完整linux发行版,chroot
  3. 基于C#的公交充值管理系统的设计与实现
  4. On error goto 捕捉错误语句
  5. 忘记路由器密码,如何恢复,简单明了
  6. java计算机毕业设计书籍影视评论系统源码+系统+数据库+lw文档+mybatis+运行部署
  7. 轻栈送上免费注册支付宝小程序
  8. 从传统劳务行业转型SaaS工具,叮叮劳务帮助解决建筑工人薪资支付问题
  9. 关于MobaXterm连接虚拟机的相关设置
  10. 大数据分析常用组件、框架、架构介绍(Hadoop、Spark、Storm、Flume、Kafka、Logstash、HDFS、HBase)