[服务或接口限流算法2/2]-固定时间窗口及滑动时间窗口算法解析

引言

上期我们解析了漏桶算法及令牌桶算法,今天我们就来一起探究一下固定时间窗口算法及滑动时间窗口算法

往期[服务或接口限流算法1/2]-漏桶算法及令牌桶算法解析

1.固定时间窗口

原理及概念

固定时间窗口限流算法是一种常见的限流策略,用于控制系统在固定时间段内允许处理的请求或事件数量。该算法通常用于保护系统免受过多请求的影响,以防止系统超载或崩溃。

这种算法的基本原理是,在每个固定的时间窗口内,系统都会记录请求的数量,并且如果超过了预设的阈值,就会拒绝处理额外的请求。一旦时间窗口结束,计数器就会被重置,系统再次开始记录新的请求数量。

如图所示

优缺点

固定时间窗口的原理相信很好理解, 通俗易懂的来讲 就是固定的时间段(时间窗口),对每一个请求进行计数, 当时间段内的请求数量超过预设的阈值,则拒绝处理请求, 当时间窗口结束, 计数器会被重置,循环往复, 有没有觉得其中的一些设计思路与漏桶算法很相似呢? 不要着急,我们先总结下优缺点,然后通过代码来跟漏桶算法比较

优点:

  1. 简单易懂、易于实现和调整,能够有效控制系统的请求处理数量,防止系统被过多请求拖垮

缺点:

  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;
    }
}

与漏桶算法相比

  1. 时间范围:

    • 固定时间窗口:时间窗口的大小是固定的,比如每5分钟为一个时间窗口。
    • 漏斗算法:漏斗算法不关注时间窗口,而是根据事件发生的顺序和频率来进行限制。
  2. 数据处理:

    • 固定时间窗口:在每个时间窗口结束时,对该时间窗口内的数据进行处理。
    • 漏斗算法:漏斗算法根据事件的发生顺序和频率进行处理,通常用于限制某种事件的频率或者执行特定的动作。
  3. 灵活性:

    • 固定时间窗口:固定时间窗口对于处理周期性事件非常适用,因为它们是固定的。
    • 漏斗算法:漏斗算法更适用于限制某种事件的频率或者执行特定的动作,而不受时间窗口的限制。

2.滑动时间窗口

原理及概念

滑动时间窗口算法将时间分为若干个固定的时间段,每个时间段称为一个桶(Bucket),每个桶记录了其时间段内的请求次数。在每个时间段结束时,滑动时间窗口算法会将最早的桶移除,并添加一个新的桶。这样,时间窗口就不断地滑动,以适应不同的请求频率。

例如,我们可以将时间分为 10 个桶,每个桶的时间段为 1 秒,那么滑动时间窗口的时间窗口大小就为 10 秒。在每个桶内,我们记录其时间段内的请求次数。当一个新的请求到达时,我们将其放入当前时间段的桶中,并统计当前时间窗口内的总请求数量。当时间窗口滑动到下一个桶时,我们将最早的桶移除,并将当前时间段的桶添加到时间窗口中。

优缺点:

滑动时间窗口算法相对于固定时间窗口算法具有以下优点:

  1. 更加灵活:滑动时间窗口算法可以适应不同的请求频率,因为它不是固定的时间窗口,而是不断地滑动的时间窗口。
  2. 更加精确:滑动时间窗口算法可以更加精确地控制请求频率,因为它可以记录每个时间段内的请求次数,而不是只记录整个时间窗口内的请求次数。
  3. 更加平滑:滑动时间窗口算法可以平滑地控制请求频率,因为它不会在时间窗口的开始和结束时出现突变。

但是,滑动时间窗口算法也存在以下缺点:

  1. 更加复杂:滑动时间窗口算法需要维护多个桶,并且需要不断地滑动时间窗口,因此相对于固定时间窗口算法更加复杂。
  2. 更加消耗资源:滑动时间窗口算法需要不断地创建和销毁桶,因此相对于固定时间窗口算法更加消耗资源。

总体来说,滑动时间窗口算法是一种比较常用和灵活的算法,它可以适应不同的请求频率,并且可以更加精确和平滑地控制请求频率。但是,它也需要更多的资源和更加复杂的实现。

如图

代码实现:

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;
    }
}

滑动时间窗口与固定时间窗口算法对比

  1. 时间范围:

    • 固定时间窗口:时间窗口的大小是固定的,比如每5分钟为一个时间窗口。
    • 滑动时间窗口:时间窗口的大小也是固定的,但是它会根据当前时间不断地滑动,覆盖最新的时间段。
  2. 窗口重叠:

    • 固定时间窗口:各个时间窗口之间没有重叠,是相互独立的。
    • 滑动时间窗口:时间窗口之间会有重叠,新的时间窗口会覆盖前一个时间窗口的一部分时间。
  3. 数据处理:

    • 固定时间窗口:在每个时间窗口结束时,对该时间窗口内的数据进行处理。
    • 滑动时间窗口:随着时间的推移,不断地对新的时间窗口内的数据进行处理,同时移除旧的时间窗口数据。
  4. 灵活性:

    • 固定时间窗口:固定时间窗口对于处理周期性事件非常适用,因为它们是固定的。
    • 滑动时间窗口:滑动时间窗口更适合处理连续流数据,因为它们可以动态地适应不断变化的数据流。

总的来说,固定时间窗口适用于处理离散的事件,而滑动时间窗口更适用于处理连续的数据流。选择哪种时间窗口算法取决于具体的应用场景和数据处理需求。

总结

以上介绍了四种主要的限流方案,可谓各有特点,还是那句话,没有最好的,只有最合适的,大家在后续的应用中尽量选择适合业务场景的限流方式,万变不离其宗. 其目的都是为了保护服务器资源的完整性以及接口防刷,同学们还可以根据上面的实现进行一些修改,使其支持分布式限流, 比如引入redis等分布式缓存框架

本人博客 夜航猩 欢迎一起讨论

相关推荐
星星电灯猴9 分钟前
iOS App安全实战:借助Ipa Guard提升应用抗逆向能力的开发者实用指南
后端
林鹿15 分钟前
Dart: 串联多个数据流
后端·架构·dart
Java水解36 分钟前
MySQL 分页查询优化
后端·mysql
想用offer打牌1 小时前
面试官拷打我线程池,我这样回答😗
java·后端·面试
用户6945295521701 小时前
国内开源版“Manus”——AiPy实测:让你的工作生活走上“智动”化
前端·后端
重庆小透明1 小时前
【从零学习JVM|第三篇】类的生命周期(高频面试题)
java·jvm·后端·学习
寻月隐君2 小时前
Rust + Protobuf:从零打造高效键值存储项目
后端·rust·github
radient2 小时前
Java/Go双修 - Go哈希表map原理
后端
陈随易2 小时前
Gitea v1.24.0发布,自建github神器
前端·后端·程序员
前端付豪2 小时前
汇丰银行技术架构揭秘:全球交易稳定背后的“微服务+容灾+零信任安全体系”
前端·后端·架构