Sentinel系列之滑动窗口、漏桶代码分析
1. 滑动窗口
-
原理
滑动窗口限流算法(Sliding Window)是对固定窗口算法的一个改进。在滑动窗口算法中,窗口大小仍然是固定的,但它把单位时间周期划分为n个小周期,分别记录每个小周期内请求的数量,根据时间滑动删除过期的小周期。
需要注意的是,当请求到达新的周期,才会往前滑动,也就是说滑动是在过了一个完整的窗口之后才开始的,不是立即开始,否则和固定窗口没什么区别。
-
Sentinel实现
StatisticNode使用了滑动窗口算法统计指标,在DefaultController中被调用,作为默认的一种限流方式。(com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController是Sentinel的流量控制算法类,DefaultController是它的一个实现)
StatisticNode持有两个ArrayMetric对象,分别是以秒为单位和以分为单位的时间窗口。限流判断用的是第一个窗口,它是1秒钟的窗口,被分成了2个小周期。
ArrayMetric持有一个LeapArray对象,LeapArray是Sentinel里面一个指标度量的基类。
它拥有一个数组,protected final AtomicReferenceArray<WindowWrap
它有两种实现,我们先看BucketLeapArray.
-
BucketLeapArray
它的一个核心方法是com.alibaba.csp.sentinel.slots.statistic.base.LeapArray#currentWindow(),在很多操作中第一步都是定位到当前的窗口周期。
具体实现如下:
a. 根据传入时间(一般是当前时间)计算出对应窗口在数组中的下标和当前窗口开始的时间
b. 取出桶元素,分三种场景进行讨论
c. 如果桶不存在,直接创建并CAS设置。
d. 如果桶存在且开始时间相相等,说明桶没有过期,直接使用
e. 桶已经过期了,要清空并重新设置窗口开始时间。没有专门线程清理过期的桶。超过一个窗口之后,会重新从下标0开始填充。
-
OccupiableBucketLeapArray
Sentinel 1.5.0 对底层的滑动窗口统计结构进行了升级,添加了“占用”机制,允许在当前 QPS 已经达到限流阈值时,同个资源高优先级的请求提前占用未来时间窗口的配额数,等待到对应时间窗口到达时直接通过,从而可以实现“最终通过”的效果而不是被立即拒绝;而同个资源低优先级的请求则不能占用未来的配额,阈值达到时就会被限流。
Sentinel 1.5.0 引入了
FutureBucketLeapArray
,这是一种特殊的滑动窗口,仅维持当前时间以后的格子,从而可以用于统计未来被预先占用的配额数目。Sentinel 将普通的滑动窗口与FutureBucketLeapArray
组合成可占用的滑动窗口OccupiableBucketLeapArray
,从而实现了“部分高优先级请求最终通过”的效果。https://sentinelguard.io/zh-cn/blog/sentinel-1-5-0-release.html
-
优点
简单易懂,精度高。
-
缺点
一旦到达限流后,请求都会直接暴力被拒绝,不够友好。
2. 漏桶
-
原理
漏桶算法(Leaky Bucket)是将请求放入一个有固定容量的“桶”中,桶内的请求以固定速率传出。当桶满时,新进入的请求将被丢弃。
漏桶算法可以保证处理请求的速率恒定,从而有效防止流量激增导致的服务不稳定,有点类似生产者消费者模式。
-
Sentinel实现
Sentinel在com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController通过漏桶算法实现了匀速处理,也就是4.2的模式。
这个模式的难点主要有两个:1.如何实现匀速处理 2.如何实现排队
对于第1点,Sentinel的实现方式是,把一个时间周期(一般是1秒)按允许通过的请求数,平均分成N个小区间,一个小区间内只允许有一个请求通过。
也就是说,每个请求可以去抢占一个小区间,如果抢到了并且不用等待超过最大等待时间,那就排队,否则拒绝。而资源端每个小区间只会处理一个请求,在一个时间周期内处理的数量也是恒定的,这就达到了匀速的目的。
对于第2点,Sentinel是使用了Thread.sleep()让线程休眠达到排队效果。
具体步骤如下:
a.使用一个字段latestPassedTime来记录当前请求已经发放到哪一个时间点
b.在每次请求到来之时,先计算它需要消耗的时间costTime(即一个小区间的大小)
c. latestPassedTime+costTime得出预期时间expectedTime
d. expectedTime-currentTime得出等待时间waitTime
e. 如果waitTime小于等于允许等待时间,则排队,否则直接拒绝
需要注意的是,latestPassedTime不是指已经通过了请求的时间点,不一定是过去的时间,它表示的是当前被抢占的区间已经到哪个时间点了,请求可以抢占未来的区间。
源码分析