1. 计算器固定窗口算法
计数器固定窗口算法是最简单的限流算法,实现方式也比较简单。就是通过维护一个单位时间内的计数值,每当一个请求通过时,就将计数值加1,当计数值超过预先设定的阈值时,就拒绝单位时间内的其他请求。如果单位时间已经结束,则将计数器清零,开启下一轮的计数。
但是这种实现会有一个问题,举个例子:
假设我们设定1秒内允许通过的请求阈值是99,如果有用户在时间窗口的最后几毫秒发送了99个请求,紧接着又在下一个时间窗口开始时发送了200个请求,那么这个用户其实在一秒内成功请求了198次,显然超过了阈值但并不会被限流。其实这就是临界值问题,那么临界值问题要怎么解决呢?
2. 计算器滑动窗口算法
计数器滑动窗口法就是为了解决上述固定窗口计数存在的问题而诞生。前面说了固定窗口存在临界值问题,要解决这种临界值问题,显然只用一个窗口是解决不了问题的。假设我们仍然设定1秒内允许通过的请求是200个,但是在这里我们需要把1秒的时间分成多格,假设分成5格(格数越多,流量过渡越平滑),每格窗口的时间大小是200毫秒,每过200毫秒,就将窗口向前移动一格
图中将窗口划为5份,每个小窗口中的数字表示在这个窗口中请求数,所以通过观察上图,可知在当前时间快(200毫秒)允许通过的请求数应该是70而不是200(只要超过70就会被限流),因为我们最终统计请求数时是需要把当前窗口的值进行累加,进而得到当前请求数来判断是不是需要进行限流。
可以发现这其实就是固定窗口算法的变种,还是不能解决2个窗口之间临界值的问题。
- 代码举例
现在给你一串数字,三个为一组向后滑动,求出和最大的一组数字。
public static void main(String[] args) {
int arr[] = {2,9,7,1,2,9,5};
//定义三个数字为一个窗口
int k = 3;
int maxcount = 0;
// 滑动窗口第一组窗口数字的值
for (int i = 0; i < k; i++) {
maxcount = arr[i] + maxcount;
}
int tempSum = maxcount;
// 滑动窗口在数组上移动
for (int end = k; end < arr.length; end++) {
// 滑动窗口的起始位置
int start = end - k + 1;
if (start + k-1 > arr.length) {
break;
}
// 更新滑动窗口的元素和
tempSum = tempSum - arr[start - 1] + arr[end];
if (tempSum > maxcount) {
maxcount = tempSum;
}
}
System.out.println("maxcount = " + maxcount);
}
3. 漏桶算法
上面所介绍的两种算法都不能非常平滑的过渡,下面就是漏桶算法登场了
什么是漏桶算法?
漏桶算法以一个常量限制了出口流量速率,因此漏桶算法可以平滑突发的流量。其中漏桶作为流量容器我们可以看做一个FIFO的队列,当入口流量速率大于出口流量速率时,因为流量容器是有限的,当超出流量容器大小时,超出的流量会被丢弃。
下图比较形象的说明了漏桶算法的原理,其中水龙头是入口流量,漏桶是流量容器,匀速流出的水是出口流量。
漏桶算法的特点:
- 漏桶具有固定容量,出口流量速率是固定常量(流出请求)
- 入口流量可以以任意速率流入到漏桶中(流入请求)
- 如果入口流量超出了桶的容量,则流入流量会溢出(新请求被拒绝)
4. 令牌漏桶算法
令牌桶算法是漏桶算法的改进版,可以支持突发流量。不过与漏桶算法不同的是,令牌桶算法的漏桶中存放的是令牌而不是流量。
那么令牌桶算法是怎么突发流量的呢?
最开始,令牌桶是空的,我们以恒定速率往令牌桶里加入令牌,令牌桶被装满时,多余的令牌会被丢弃。当请求到来时,会先尝试从令牌桶获取令牌(相当于从令牌桶移除一个令牌),获取成功则请求被放行,获取失败则阻塞活拒绝请求。
令牌桶算法的特点
- 最多可以存发b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃
- 每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。
令牌桶算法限制的是平均流量,因此其允许突发流量(只要令牌桶中有令牌,就不会被限流)