3. 固定窗口计数器算法:原理详解、在金融秒杀场景下的实现与临界问题分析

3.1 算法原理与核心思想

固定窗口计数器(Fixed Window Counter)是限流算法中最直观、实现成本最低的一种。其核心思想是将时间划分为固定的窗口(例如 1 秒),每个窗口内维护一个计数器,当请求到达时计数器加 1;若计数器超过预设阈值,则拒绝后续请求;窗口结束时计数器归零,进入下一个窗口。

适用场景: 金融秒杀、红包雨、新股申购等对瞬时突发流量容忍度较低,但对实现简单性和性能要求极高的场景。
维度 说明
窗口大小 固定时间长度,如 1s、5s、1min,通常与业务粒度对齐
计数器 每个窗口独立计数,窗口切换时原子重置
阈值 窗口内允许的最大请求数,超过即触发限流
拒绝策略 直接返回 429 Too Many Requests 或降级返回
固定窗口计数器算法流程 窗口 1 (0~1s) 计数器: 0 → 阈值 窗口 2 (1~2s) 计数器: 0 → 阈值 窗口 3 (2~3s) 计数器: 0 → 阈值 请求 拒绝 每个窗口独立计数,窗口切换时计数器原子归零 窗口内累计请求超过阈值则直接拒绝,直到下一个窗口开启

3.2 金融秒杀场景下的实现方案

在金融秒杀场景中,固定窗口计数器通常配合 Redis + Lua 脚本实现原子计数,避免并发竞争。以下是一个典型的秒杀接口限流实现:

-- Lua 脚本:固定窗口计数器限流
-- KEYS[1] = 限流 key,例如 "seckill:12345:window"
-- ARGV[1] = 窗口大小(毫秒)
-- ARGV[2] = 阈值
-- ARGV[3] = 当前时间戳(毫秒)

local window_key = KEYS[1]
local window_size = tonumber(ARGV[1])
local threshold = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

-- 计算当前窗口起始时间
local window_start = math.floor(now / window_size) * window_size
local window_key_full = window_key .. ":" .. tostring(window_start)

-- 原子递增并获取当前计数
local current = redis.call("INCR", window_key_full)

-- 如果是第一次创建,设置过期时间
if current == 1 then
    redis.call("PEXPIRE", window_key_full, window_size)
end

-- 判断是否超过阈值
if current > threshold then
    return 0  -- 限流
else
    return 1  -- 放行
end
实现要点:
  • 窗口 key 必须包含窗口起始时间戳,确保不同窗口独立计数
  • 使用 PEXPIRE 设置过期时间,避免 key 长期占用内存
  • Lua 脚本保证 INCR 和 PEXPIRE 的原子性,无需分布式锁

3.3 临界问题(Traffic Burst at Window Boundary)

固定窗口计数器最著名的缺陷是窗口边界突发问题。由于计数器在窗口切换时瞬间归零,攻击者或突发流量可以在窗口末尾和下一个窗口开头集中发送请求,导致实际通过量达到阈值的 2 倍。

临界问题危害: 假设阈值 = 100 次/秒,攻击者在 0.9s~1.0s 发送 100 个请求,在 1.0s~1.1s 再发送 100 个请求。实际 0.2 秒内通过了 200 个请求,完全突破了限流保护。
固定窗口临界突发问题示意(阈值=100/秒) 窗口 1 (0~1s) 已通过: 100 请求(满) 窗口 2 (1~2s) 已通过: 100 请求(满) 边界 0.2 秒内集中通过 200 请求 窗口 1 末尾(0.9~1.0s)通过 100 请求 + 窗口 2 开头(1.0~1.1s)通过 100 请求 实际瞬时 QPS 达到 1000,远超阈值 100,导致后端过载

3.4 临界问题的影响与应对策略

影响维度 具体表现 严重程度
数据库连接池 突发请求耗尽连接池,导致正常请求超时 高
缓存击穿 热点 key 在窗口边界被大量请求穿透 中
下游服务 支付、风控等依赖服务瞬时压力翻倍 高
业务一致性 秒杀超卖、重复扣款风险增加 极高
应对策略:
  • 滑动窗口替代: 将固定窗口拆分为更细粒度的子窗口(如 1s 拆为 10 个 100ms 子窗口),平滑边界流量
  • 预热 + 预分配: 在秒杀开始前预先分配令牌,避免窗口切换时的瞬时竞争
  • 两层限流: 固定窗口作为第一层粗粒度防护,配合令牌桶或漏桶作为第二层精细控制
  • 随机化窗口偏移: 对每个用户或请求使用不同的窗口起始偏移,分散边界压力

3.5 适用场景与局限性总结

评估项 评价
实现复杂度 极低,适合快速上线
性能开销 极低,仅需一次 Redis INCR
内存占用 低,每个窗口仅一个 key
临界突发防护 弱,窗口边界存在 2 倍阈值漏洞
流量平滑度 差,窗口内允许任意突发
推荐场景 非核心接口、监控告警、粗粒度限流
架构建议: 在金融秒杀场景中,固定窗口计数器适合作为前置快速过滤层,配合滑动窗口或令牌桶作为精确限流层。切勿单独依赖固定窗口保护核心交易链路。