算法原理
1、(固定)时间窗限流算法
- 特点:系统自动选定一个时间窗口的起始零点,然后按照固定长度将时间轴划分为若干定长的时间窗口。
- 原理:判断请求到达的时间点所在的时间窗口当前统计的数据是否超出阈值。
- 缺点:跨窗口范围的数据超出阈值问题。
2、 滑动时间窗限流算法
- 特点:没有划分固定的时间窗起点与终点,而是将每一次请求的到来时间点作为统计时间窗的终点,起点则是终点向前推时间窗长度的时间点。
- 原理:判断 请求到来时间点 - 窗口长度 范围内数据是否超出阈值。
- 缺点:重复统计、性能问题。
3、(改进)滑动时间窗限流算法
- 特点:固定时间窗+滑动时间窗结合,时间窗分为若干“样本窗口”。
- 原理:每个样本窗口会统计一次数据并记录下来。当一个请求到达时,会统计出当前请求时间点所在样本窗口中的流量数据,然后加上时间窗中其它样本窗口的统计数据,判断是否超出阈值。
- 缺点:不够精确。只是时间窗口被细粒度化了,不准确性降低很多而已。
核心源码
一、数据统计
核心类:
- StatisticSlot - 统计入口
- DefaultNode - 实际入口
- StatisticNode - 统计节点
- ArrayMetric - 使用数组保存数据的计量器类
- LeapArray - 样本窗口数组(环性数组)
- BucketLeapArray - 重置样本窗口
- WindowWrap - 样本窗口(泛型T为MetricBucket)
- MetricBucket - 统计数据封装类(多维度,维度类型在MetricEvent枚举)
1、StatisticSlot - 统计入口
用于记录、统计不同纬度的 runtime 指标监控信息;(做实时统计)
- 线程数:内部维护一个LongAdder来进行当前线程数的统计,每进一个请求+1,每释放一个请求-1。
- QPS:通过滑动时间窗统计请求数量是否超过阈值。
主要做3件事:
- 1、通过node中的当前的实时统计指标信息进行规则校验
- 2、如果通过了校验,则重新更新node中的实时指标数据
- 3、如果被block或出现了异常了,则重新更新node中block的指标或异常指标
1 | java复制代码@Override |
2、DefaultNode - 实际入口
统计资源当前入口和全局数据
- DefaultNode:保存着某个resource在某个context中的实时指标,每个DefaultNode都指向一个ClusterNode
- ClusterNode:保存着某个resource在所有的context中实时指标的总和,同样的resource会共享同一个ClusterNode,不管他在哪个context中
1 | java复制代码@Override |
3、StatisticNode - 统计节点
滑动计数器按 秒/分 分别增加统计数据
1 | java复制代码// 定义一个使用数组保存数据的计量器:样本窗口数-2、时间窗默认值-1000ms |
4、ArrayMetric - 使用数组保存数据的计量器类
按秒/分统计数据并记录到当前样本窗口
1 | java复制代码@Override |
5、LeapArray - 样本窗口数组(环性数组)
获取当前时间点所在的样本窗口(LeapArray采用了一个环性数组的数据结构,和一致性hash算法的图类似)
- 1.根据当前时间,算出该时间的timeId,并根据timeId算出当前窗口在采样窗口数组中的索引idx(时间每增长一个windowLength的长度,timeId就加1:但是idx不会增长,只会在0和1之间变换,因为array数组的长度是2)
- 2.根据当前时间算出当前窗口的应该对应的开始时间time,以毫秒为单位
- 3.根据索引idx,在采样窗口数组中取得一个时间窗口old,然后判断处理
1 | csharp复制代码public WindowWrap<T> currentWindow(long timeMillis) { |
假设:时间从0开始
- 500ms内:时间窗口不会向前滑动(timeId1),当前窗口的开始时间(0),时间窗=timeId1+timeId2
- 500~1000ms,时间窗口就会向前滑动到下一个(timeId2),这时会更新当前窗口的开始时间(500),时间窗=timeId1+timeId2
- 超过1000ms时:再次进入下一个时间窗口(timeId3),更新当前窗口的开始时间(1000),时间窗(此时arrays数组中的窗口将会有一个失效)=timeId2+timeId3
1 | ini复制代码/** |
6、BucketLeapArray - 重置样本窗口
计算出的样本窗口已经过时:重置原时间窗口(替换老的样本窗口)
1 | java复制代码@Override |
7、MetricBucket - 统计数据封装类
pass维度增加
1 | csharp复制代码public void addPass(int n) { |
二、数据使用(qps例子)
核心类:
- DefaultController - 入口
- StatisticNode - 实际入口
- ArrayMetric - 使用数组保存数据的计量器类
- LeapArray - 样本窗口数组(环性数组)
1、DefaultController - 入口
获取当前时间窗已统计数据
1 | csharp复制代码private int avgUsedTokens(Node node) { |
2、StatisticNode - 实际入口
获取通过qps数量
1 | java复制代码// 定义一个使用数组保存数据的计量器:样本窗口数-2、时间窗默认值-1000ms |
3、ArrayMetric - 使用数组保存数据的计量器类
汇总pass数据:所有样本窗口
1 | typescript复制代码@Override |
4、LeapArray - 样本窗口数组(环性数组)
汇总样本窗口实例:要过滤过时的
1 | arduino复制代码public List<T> values(long timeMillis) { |
参考资料
本文转载自: 掘金