3. 限流算法(上):固定窗口计数器算法原理、滑动窗口日志算法原理、滑动窗口计数器算法原理

在模型 Serving 层,限流算法的选择直接决定了流量整形效果与资源保护能力。本章聚焦三种最基础的窗口类限流算法:固定窗口计数器、滑动窗口日志 与 滑动窗口计数器。理解它们的原理、边界与适用场景,是后续学习令牌桶、漏桶等高级算法的基础。

3.1 固定窗口计数器算法

固定窗口计数器是最直观的限流实现。它将时间划分为固定的窗口(如 1 秒),每个窗口内维护一个计数器,请求到达时计数器加 1;当计数器超过阈值时,拒绝后续请求;窗口结束时计数器归零。

3.1.1 核心流程

  1. 定义窗口大小 W(如 1000ms)与阈值 limit(如 10)。
  2. 记录当前窗口起始时间戳 windowStart 与计数器 counter。
  3. 请求到达时,判断当前时间是否已超出 windowStart + W:
    • 若超出,重置 windowStart 为当前时间,counter 归零。
    • 若未超出,检查 counter < limit:是则放行并递增;否则拒绝。

3.1.2 临界问题(Traffic Burst at Boundary)

⚠ 经典缺陷:固定窗口在窗口切换瞬间可能出现两倍阈值的突发流量。例如窗口大小 1s、阈值 10,若前窗口最后 100ms 内通过 10 个请求,后窗口开始 100ms 内又通过 10 个请求,则 200ms 内实际通过了 20 个请求,远超阈值。
固定窗口计数器参数示例
参数值说明
窗口大小1000 ms时间粒度
阈值10 req每窗口最大请求数
计数器int原子变量或 Redis INCR
重置策略窗口到期归零硬边界

3.1.3 适用场景

  • 对突发不敏感、允许短时毛刺的业务(如日志上报、非关键指标采集)。
  • 实现简单,适合客户端限流或低精度保护。
  • 配合预热或排队机制可缓解临界问题,但无法根除。

3.2 滑动窗口日志算法

滑动窗口日志(Sliding Window Log)通过记录每个请求的时间戳,在每次请求时统计当前时间往前一个窗口长度内的请求总数,从而精确控制任意时间窗口内的流量。它彻底解决了固定窗口的临界突发问题,但代价是存储与计算开销较高。

3.2.1 核心流程

  1. 维护一个有序集合(如 Redis ZSET、TreeSet),存储每个请求的时间戳。
  2. 请求到达时,计算窗口左边界 now - windowSize。
  3. 移除集合中所有小于左边界的过期时间戳(清理)。
  4. 统计集合中剩余元素数量 count。
  5. 若 count < limit,将当前时间戳加入集合并放行;否则拒绝。

3.2.2 优缺点对比

维度说明
精度理论最高,任意时刻窗口内请求数精确可控
内存占用O(N),N 为窗口内请求数,高 QPS 下增长快
清理开销每次请求需扫描过期数据,CPU 消耗随窗口大小线性增长
分布式友好依赖 Redis ZSET 可做全局限流,但需注意大 Key 问题
💡 优化建议:可设置异步定时清理过期日志,减少每次请求的扫描量;或使用近似算法(如滑动窗口计数器)降低精度换取性能。

3.3 滑动窗口计数器算法

滑动窗口计数器(Sliding Window Counter)是固定窗口与滑动窗口日志的折中方案。它将窗口划分为多个小格子(sub-window),每个格子独立计数;滑动时仅统计当前时间所在格子及之前 N-1 个格子的总和,实现近似滑动效果。常见实现如 Redis 的 CL.THROTTLE 命令。

3.3.1 核心流程

  1. 将窗口 W 划分为 N 个格子,每个格子时长 W/N(如 1s 窗口分 10 格,每格 100ms)。
  2. 每个格子独立计数器,使用当前时间戳对格子时长取模定位格子索引。
  3. 请求到达时,计算当前格子索引,累加当前格子计数器。
  4. 统计当前格子及前 N-1 个格子的计数器总和。
  5. 若总和 < limit,放行;否则拒绝。

3.3.2 格子粒度与精度

格子数 N精度内存开销典型场景
2低(接近固定窗口)极低快速原型
10中(误差约 10%)低生产常用
100高(误差约 1%)中高精度要求
📐 误差分析:滑动窗口计数器的误差来源于格子边界处的请求归属。当请求落在格子中间时,该格子被完整计入当前窗口,但实际该格子中部分请求可能属于上一个窗口。格子数越多,误差越小,但内存与计算开销线性增长。

3.4 三种算法对比总览

算法精度内存开销临界突发实现复杂度推荐场景
固定窗口计数器低O(1)有极低低精度、低并发、客户端限流
滑动窗口日志最高O(N)无高高精度、低 QPS 或可接受清理开销
滑动窗口计数器中-高O(N)基本消除中生产环境通用,平衡精度与性能

3.5 算法原理对比图

下图展示了三种算法在时间轴上的窗口划分方式与计数逻辑差异,帮助直观理解滑动与固定的本质区别。

三种窗口限流算法原理对比 三种窗口限流算法原理对比 当前窗口/格子 过期/拒绝请求 放行请求 固定窗口计数器 窗口 1 (0-1000ms) 窗口 2 (1000-2000ms) ⚠ 边界突发 滑动窗口日志 滑动窗口 (当前时间 - 窗口大小) 过期 当前时间 滑动窗口计数器 (N=5) 格子0 格子1 格子2 格子3 格子4 (当前) 滑动窗口 = 格子0 + 格子1 + 格子2 + 格子3 + 格子4 cnt:3 cnt:5 cnt:2 cnt:4 cnt:1 📌 关键差异 • 固定窗口:硬边界,临界突发 • 滑动日志:精确但高存储 • 滑动计数器:格子化近似, 平衡精度与性能 ⚙ 实现提示 • 固定窗口:AtomicLong + 时间戳 • 滑动日志:Redis ZSET + ZREMRANGEBYSCORE • 滑动计数器:Redis Hash + 时间槽取模

3.6 选型建议

🎯 生产推荐:对于大多数模型 Serving 场景,滑动窗口计数器(N=10~20)是性价比最高的选择。它消除了固定窗口的临界突发问题,内存与 CPU 开销可控,且容易在 Redis 或本地内存中实现。若业务对精度有极致要求且 QPS 较低(如 <1000/s),可选用滑动窗口日志;若仅需粗粒度保护且允许短时毛刺,固定窗口计数器仍可胜任。

3.7 本章小结

  • 固定窗口计数器:实现最简单,但存在窗口边界突发流量问题,适合低精度场景。
  • 滑动窗口日志:精度最高,通过记录每个请求时间戳实现精确滑动,但存储与清理开销大。
  • 滑动窗口计数器:折中方案,将窗口划分为多个格子,统计格子总和实现近似滑动,是生产环境的主流选择。
  • 三种算法在精度、内存、实现复杂度上形成梯度,选型时应结合业务 QPS、精度要求与基础设施能力综合判断。