3. 固定窗口计数器算法:原理详解、在金融秒杀场景下的实现与临界问题分析
3.1 算法原理与核心思想
固定窗口计数器(Fixed Window Counter)是限流算法中最直观、实现成本最低的一种。其核心思想是将时间划分为固定的窗口(例如 1 秒),每个窗口内维护一个计数器,当请求到达时计数器加 1;若计数器超过预设阈值,则拒绝后续请求;窗口结束时计数器归零,进入下一个窗口。
适用场景: 金融秒杀、红包雨、新股申购等对瞬时突发流量容忍度较低,但对实现简单性和性能要求极高的场景。
| 维度 | 说明 |
|---|---|
| 窗口大小 | 固定时间长度,如 1s、5s、1min,通常与业务粒度对齐 |
| 计数器 | 每个窗口独立计数,窗口切换时原子重置 |
| 阈值 | 窗口内允许的最大请求数,超过即触发限流 |
| 拒绝策略 | 直接返回 429 Too Many Requests 或降级返回 |
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 个请求,完全突破了限流保护。
3.4 临界问题的影响与应对策略
| 影响维度 | 具体表现 | 严重程度 |
|---|---|---|
| 数据库连接池 | 突发请求耗尽连接池,导致正常请求超时 | 高 |
| 缓存击穿 | 热点 key 在窗口边界被大量请求穿透 | 中 |
| 下游服务 | 支付、风控等依赖服务瞬时压力翻倍 | 高 |
| 业务一致性 | 秒杀超卖、重复扣款风险增加 | 极高 |
应对策略:
- 滑动窗口替代: 将固定窗口拆分为更细粒度的子窗口(如 1s 拆为 10 个 100ms 子窗口),平滑边界流量
- 预热 + 预分配: 在秒杀开始前预先分配令牌,避免窗口切换时的瞬时竞争
- 两层限流: 固定窗口作为第一层粗粒度防护,配合令牌桶或漏桶作为第二层精细控制
- 随机化窗口偏移: 对每个用户或请求使用不同的窗口起始偏移,分散边界压力
3.5 适用场景与局限性总结
| 评估项 | 评价 |
|---|---|
| 实现复杂度 | 极低,适合快速上线 |
| 性能开销 | 极低,仅需一次 Redis INCR |
| 内存占用 | 低,每个窗口仅一个 key |
| 临界突发防护 | 弱,窗口边界存在 2 倍阈值漏洞 |
| 流量平滑度 | 差,窗口内允许任意突发 |
| 推荐场景 | 非核心接口、监控告警、粗粒度限流 |
架构建议: 在金融秒杀场景中,固定窗口计数器适合作为前置快速过滤层,配合滑动窗口或令牌桶作为精确限流层。切勿单独依赖固定窗口保护核心交易链路。