引言
上期我们解析了漏桶算法及令牌桶算法,今天我们就来一起探究一下固定时间窗口算法及滑动时间窗口算法
1.固定时间窗口
原理及概念
固定时间窗口限流算法是一种常见的限流策略,用于控制系统在固定时间段内允许处理的请求或事件数量。该算法通常用于保护系统免受过多请求的影响,以防止系统超载或崩溃。
这种算法的基本原理是,在每个固定的时间窗口内,系统都会记录请求的数量,并且如果超过了预设的阈值,就会拒绝处理额外的请求。一旦时间窗口结束,计数器就会被重置,系统再次开始记录新的请求数量。
如图所示

优缺点
固定时间窗口的原理相信很好理解, 通俗易懂的来讲 就是固定的时间段(时间窗口),对每一个请求进行计数, 当时间段内的请求数量超过预设的阈值,则拒绝处理请求, 当时间窗口结束, 计数器会被重置,循环往复, 有没有觉得其中的一些设计思路与漏桶算法很相似呢? 不要着急,我们先总结下优缺点,然后通过代码来跟漏桶算法比较
优点:
- 简单易懂、易于实现和调整,能够有效控制系统的请求处理数量,防止系统被过多请求拖垮
缺点:
-
无法应对突发流量和周期性波动,可能导致一些请求在时间窗口开始时集中到来,而在结束时无法处理所有请求的情况。
其实很好理解这段话,结合上图能清晰的解释, 假设两个临近的时间窗口t2,t3. 在t2窗口期结束前涌进了N(N < 阈值)个请求,然后t2时间窗口到期计数器被重置,然后有一批请求涌入了t3窗口期的开始时段, 导致短时间内的大量请求涌入,可能会造成服务器过载
代码实现
请结合以上内容及代码注释进行阅读,更容易理解
arduino
public class FixedTimeWindowCurrentLimitHandler implements CurrentLimitHandler {
/**
* 窗口长度
*/
private final long windowSizeInMs;
/**
* 计数器
*/
private final AtomicInteger counter;
/**
* 窗口开始时间
*/
private volatile long windowStartTime;
public FixedTimeWindowCurrentLimitHandler(long windowSizeInMs) {
this.windowSizeInMs = windowSizeInMs;
this.counter = new AtomicInteger(0);
this.windowStartTime = System.currentTimeMillis();
}
@Override
public synchronized boolean tryAccess(Integer token) {
// 获取当前请求时间戳
long currentTime = System.currentTimeMillis();
/*
是否在窗口内 当前时间 - 窗口开始时间 > 窗口长度
假设当前时间为120, 窗口开始时间为 100, 窗口长度为100
则 120 - 100 = 20 , 小于 时间窗口长度, 如果大于, 则表示超出时间窗口 则表示本次请求在窗口内
*/
if (currentTime - windowStartTime > windowSizeInMs) {
// 不在当前窗口内, 重置窗口起始时间和计数器
windowStartTime = currentTime;
counter.set(0);
}
// 检查计数器是否超过限制,例如,限制每个时间窗口内的请求数量为 100
return counter.incrementAndGet() <= 100;
}
}
与漏桶算法相比
-
时间范围:
- 固定时间窗口:时间窗口的大小是固定的,比如每5分钟为一个时间窗口。
- 漏斗算法:漏斗算法不关注时间窗口,而是根据事件发生的顺序和频率来进行限制。
-
数据处理:
- 固定时间窗口:在每个时间窗口结束时,对该时间窗口内的数据进行处理。
- 漏斗算法:漏斗算法根据事件的发生顺序和频率进行处理,通常用于限制某种事件的频率或者执行特定的动作。
-
灵活性:
- 固定时间窗口:固定时间窗口对于处理周期性事件非常适用,因为它们是固定的。
- 漏斗算法:漏斗算法更适用于限制某种事件的频率或者执行特定的动作,而不受时间窗口的限制。
2.滑动时间窗口
原理及概念
滑动时间窗口算法将时间分为若干个固定的时间段,每个时间段称为一个桶(Bucket),每个桶记录了其时间段内的请求次数。在每个时间段结束时,滑动时间窗口算法会将最早的桶移除,并添加一个新的桶。这样,时间窗口就不断地滑动,以适应不同的请求频率。
例如,我们可以将时间分为 10 个桶,每个桶的时间段为 1 秒,那么滑动时间窗口的时间窗口大小就为 10 秒。在每个桶内,我们记录其时间段内的请求次数。当一个新的请求到达时,我们将其放入当前时间段的桶中,并统计当前时间窗口内的总请求数量。当时间窗口滑动到下一个桶时,我们将最早的桶移除,并将当前时间段的桶添加到时间窗口中。
优缺点:
滑动时间窗口算法相对于固定时间窗口算法具有以下优点:
- 更加灵活:滑动时间窗口算法可以适应不同的请求频率,因为它不是固定的时间窗口,而是不断地滑动的时间窗口。
- 更加精确:滑动时间窗口算法可以更加精确地控制请求频率,因为它可以记录每个时间段内的请求次数,而不是只记录整个时间窗口内的请求次数。
- 更加平滑:滑动时间窗口算法可以平滑地控制请求频率,因为它不会在时间窗口的开始和结束时出现突变。
但是,滑动时间窗口算法也存在以下缺点:
- 更加复杂:滑动时间窗口算法需要维护多个桶,并且需要不断地滑动时间窗口,因此相对于固定时间窗口算法更加复杂。
- 更加消耗资源:滑动时间窗口算法需要不断地创建和销毁桶,因此相对于固定时间窗口算法更加消耗资源。
总体来说,滑动时间窗口算法是一种比较常用和灵活的算法,它可以适应不同的请求频率,并且可以更加精确和平滑地控制请求频率。但是,它也需要更多的资源和更加复杂的实现。
如图

代码实现:
arduino
public class SlidingTimeWindowCurrentLimitHandler implements CurrentLimitHandler {
/**
* 时间窗口大小,单位为秒
*/
private int windowSize;
/**
* 时间窗口内的请求限制
*/
private int limit;
/**
* 用于存储每个时间段的请求时间戳, 双端队列, 对应概念中的桶
*/
private Deque<Long> deque;
public SlidingTimeWindowCurrentLimitHandler(int windowSize, int limit) {
this.windowSize = windowSize;
this.limit = limit;
this.deque = new ArrayDeque<>();
}
@Override
public synchronized boolean tryAccess(Integer token) {
// 当前时间戳,单位为秒
long currentTime = System.currentTimeMillis() / 1000;
/*
如果队列不为空, 且最久的请求时间 小于 等于 当前时间 - 时间窗口大小
举例
最后一个请求时间为100, 当前时间为 120 , 窗口长度为100
则: 120 - 100 < 100, 则表示还是窗口内, 否则在窗口之外, 然后移除最远的那个请求时间
*/
while (!deque.isEmpty() && deque.peekFirst() <= currentTime - windowSize) {
// 移除第一个元素
deque.pollFirst();
}
// 进行了上述处理后, 如果时间窗口内的请求次数仍然超过了限制,则拒绝请求
if (deque.size() >= limit) {
return false;
}
// 同时记录当前请求的时间戳添加至队尾
deque.offerLast(currentTime);
return true;
}
}
滑动时间窗口与固定时间窗口算法对比
-
时间范围:
- 固定时间窗口:时间窗口的大小是固定的,比如每5分钟为一个时间窗口。
- 滑动时间窗口:时间窗口的大小也是固定的,但是它会根据当前时间不断地滑动,覆盖最新的时间段。
-
窗口重叠:
- 固定时间窗口:各个时间窗口之间没有重叠,是相互独立的。
- 滑动时间窗口:时间窗口之间会有重叠,新的时间窗口会覆盖前一个时间窗口的一部分时间。
-
数据处理:
- 固定时间窗口:在每个时间窗口结束时,对该时间窗口内的数据进行处理。
- 滑动时间窗口:随着时间的推移,不断地对新的时间窗口内的数据进行处理,同时移除旧的时间窗口数据。
-
灵活性:
- 固定时间窗口:固定时间窗口对于处理周期性事件非常适用,因为它们是固定的。
- 滑动时间窗口:滑动时间窗口更适合处理连续流数据,因为它们可以动态地适应不断变化的数据流。
总的来说,固定时间窗口适用于处理离散的事件,而滑动时间窗口更适用于处理连续的数据流。选择哪种时间窗口算法取决于具体的应用场景和数据处理需求。
总结
以上介绍了四种主要的限流方案,可谓各有特点,还是那句话,没有最好的,只有最合适的,大家在后续的应用中尽量选择适合业务场景的限流方式,万变不离其宗. 其目的都是为了保护服务器资源的完整性以及接口防刷,同学们还可以根据上面的实现进行一些修改,使其支持分布式限流, 比如引入redis等分布式缓存框架
本人博客 夜航猩 欢迎一起讨论