Sentinel限流及其滑动窗口算法
Sentinel的限流原理
限流效果,对应有DefaultController快速失败
WarmUpController慢启动(令牌桶算法)
RateLimiterController(漏桶算法)
滑动时间窗口算法
- 固定时间窗口算法
即比如每一秒作为一个固定的时间窗口,在一秒内最多可以通过100个请求,那么在统计数据的时候,如果0-500ms没有请求,而500-1000ms有100个请求,那么这一百个请求都能通过,在1000-1500ms的时候,又有100个请求过来了,它依然能够通过,因为在1000ms的时候又开启了一个新的固定时间窗口。这样,500-1500ms这一秒内有了200个请求,但是它依然能够通过,所以这就会造成数据统计的不准确性,并不能保证在任意的一秒内都使得通过请求数小于100,。 - 普通的滑动窗口做法
因为固定时间窗口带来的数据同的不准确性,就会造成可能局部的时间压力过高,所以就需要采用滑动窗口算法来进行统计,滑动窗口时间算法意思就是,从请求过来的时刻开始,统计往前一秒中的数据,通过这个数据来判断是否进行限流等操作。这样的话准确性就会有很大的提升,但是由于每一次请求过来都需要重新统计前一秒的数据,就会造成巨大的性能损失。所以这也是他的不合理的地方。 - Sentinel的滑动时间窗口算法
由于固定时间窗口带来的不准确性和普通滑动窗口带来的性能损失的缺点,所以Sentinel对这两种方案采取了折中的方案。
在Sentinel中会将原本的固定的时间窗口划分成很多更小的样本窗口,每一次请求的数据都会被保存在小的样本窗口中去,而每一次获取的时候都会去获取这些样本时间窗口中的数据,从而不需要进行重新统计,就减小了性能损耗,同时时间窗口被细粒度化了,不准确性也会降低很多。
在统计插槽StatisticsSlot类中有ArrayMetric的类的成员变量,用于统操作和获取统计数据,而ArrayMetric类有一个成员变量LeapArray data,并提供了一些操作这个成员变量data信息的方法。
LeapArray提供两个参数sampleCount 样本数量,intervalInMs 间隔时间,意思就是在这一段的间隔时间内,被分成了sampleCount 个样本去分别进行统计,默认间隔时间是1s,sampleCount 是2。
下面看看LeapArray的一部分源码,它也是实现滑动窗口最为重要的地方。
public abstract class LeapArray<T> {
//每一个样本窗口的时间长度
protected int windowLengthInMs;
//一个滑动窗口被划分成了多少个样本
protected int sampleCount;
//时间窗口的时间长度
protected int intervalInMs;
private double intervalInSecond;
//一个样本窗口数组,将一个滑动窗口划分为sampleCount个样本窗口的数组
protected final AtomicReferenceArray<WindowWrap<T>> array;
//部分代码省略
}
在LeapArray有这几个成员变量:
- 每一个样本窗口的时间长度
- 一个滑动窗口被划分成的样本数量
- 滑动时间窗口的时间长度
- 样本窗口数组
public WindowWrap<T> currentWindow(long timeMillis) {
if (timeMillis < 0) {
return null;
}
//计算当前样本窗口的在array中的索引,以当前时间除以样本时间长度,获得timeId
//再将TimeId对数组长度取余,得到索引,这就相当于把array当做了一个样本窗口圆环,就像官网上的图一样
int idx = calculateTimeIdx(timeMillis);
// 计算当前时间的样本窗口的开始时间
long windowStart = calculateWindowStart(timeMillis);
while (true) {
WindowWrap<T> old = array.get(idx);
if (old == null) {
//如果原来这个位置的样本窗口就是null的,就说明以前还没有网array的这个位置放过样本窗口,
// 这时就新建一个样本窗口用cas操作放到数组的这个位置,并返回该窗口
WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs