限流的目的:保证系统只接受承载范围内的请求流量,防止被过载的流量压崩溃。
限流算法有哪些,这边列出常见的3种:
- 令牌桶
- 漏桶
- 滑动窗口
这篇文章给大家分享一下我对令牌桶算法的理解,以及RateLimiter是如何实现令牌桶算法的。
算法过程示意图:
图解 RateLimiter.create(500):
- 左下角的桶只能放500个令牌
为什么是500呢?因为入参代表每秒限制500请求,桶里放的令牌数就是每秒的最大限制
为什么会超出500?如果长时间没人来拿令牌,1秒后不就放满了吗。就像接口一段时间没人请求,一下子来一堆人也只能是500个人同时访问。流量是不能累积超过最大限制的。 - 传送带持续匀速旋转,每隔2毫秒向桶内掉进一个令牌
说明:RateLimiter内有关时间单位的计算是精确到μs微秒的
为什么是2毫秒呢,因为1秒 平均分到 500个令牌,每个令牌就是2毫秒,这样1秒钟就可以把桶装满啦。 - 图中的时钟是个抽象元素,可以理解成限制用户当前是不是可以请求,或者说在几点时可以去拿令牌
场景化串联一下整个拿令牌的过程:
RateLimiter r = RateLimiter.create(500); // 设置个每秒500的限制
- 用户A来拿令牌
r.tryAcquire() # 默认拿一个令牌
一次拿多个令牌的应用场景在于想用一个流控限制器限制不同接口的累计流量,比如有A、B两接口;A接口消耗的系统资源或处理时间是B接口的两倍,这样在访问A接口时,应用r.acquire(2) 或 r.tryAcquire(2) 来获取2个令牌 。 - 用户走到时钟前看了看时间,现在能请求吗?
IF(当前时间>可请求时间)可请求 ELSE 不可请求 - 我们假设第1步用户A是想拿4个令牌,此时桶里只有1个令牌,那按图中的逻辑是要从传送带上再就近取3个了。(如图2)
- 此时图上 时钟的时间 = NOW+ 3 * 2ms; 也就是6ms之后,桶里才会有令牌
- 这时候用户B几乎同时来请求拿一个令牌就会失败
- 但很快1s过后就会几乎装满,又可以支持很多人并发访问了
从上面的操作过程可以分析,tryAcquire(N) 还是应该根据接口的实际消耗和权重占比来传个合理值的。不然传一个过大的会导致使用同一流控限制器的接口在一段时间的获取不到令牌。
![](https://i-blog.csdnimg.cn/blog_migrate/d480ce4890c7525211a76bff05de5243.png)
借助研读RateLimiter源码,谈谈有关需求到实现的思考:
上面的技术需求在描述上其实是比较简单的,目的也比较明确。就是这样一个简单又明确的需求,也会有无数种不同的实现。但正解的实现应该是尽量接近计算机运行效率最优解的实现,用计算机能非常好理解的方式去实现。RateLimiter就作了很好的示范。
比如令牌桶的这每秒钟的最大流量限制,随着时间推移即要恢复流量又要计算当前使用量,可能很多开发小伙伴就会首先想到用定时器来刷了,这样就陷入了需求陷阱,效率甚至功能可用性上都可能会大打折扣。那我们看一下RateLimiter怎么用这最重要的4个变量 + 一个时间(也就是我上图所画的时钟)来实现了高性能的流量限制器的吧。
整个请求令牌的过程,可以总结为4个变量计算的过程,而这几个简单的运算几乎在1微秒以内,也就是因为限流给接口带来的额外消耗可以忽略不计。
兄弟们觉得有收获就点个赞哦!你们的鼓励是我前进的动力。大家可以一起分享讨论技术学习心得,从分享中学习,快乐中进步!