Redis中MULTI/EXEC包裹INCR、EXPIRE NX命令的竞态条件在哪?
该单key限流方案的竞态条件分析
这个方案的问题出在计数器的过期时间和预期限流窗口无法严格对齐,临界时间点的并发请求会触发逻辑异常,具体触发路径如下:
- 我们先明确前提:通常限流需求分为两种,要么是固定周期限流(比如每自然秒最多允许N次请求),要么是滑动窗口限流(任意连续1秒最多允许N次请求),该方案两种场景都存在逻辑漏洞。
- 触发竞态的核心场景是计数器key刚好到期的临界时间点:
- 假设限流阈值为每秒1次,某IP的限流key还有1毫秒就过期,此时来了第一个请求,
INCR(ip)计数加1得到返回值1,判断未超限允许访问;EXPIRE ip 1 NX因为key已经有过期时间,执行失败,1毫秒后key正常过期。 - 刚好在key过期的同一毫秒,来了第二个和第三个请求,Redis单线程会串行处理两个请求的事务:
- 先处理第二个请求的事务:Redis先删除已过期的key,
INCR(ip)创建新的key得到返回值1,允许访问;EXPIRE ip 1 NX执行成功,给新key设置1秒过期。 - 再处理第三个请求的事务:key还在有效期内,
INCR(ip)得到返回值2,判断超过阈值直接拒绝;EXPIRE ip 1 NX因为key已存在过期时间执行失败。
- 先处理第二个请求的事务:Redis先删除已过期的key,
- 本质上第二个和第三个请求都属于新的限流窗口,本应该都被允许,但第三个请求被错误限流。
- 假设限流阈值为每秒1次,某IP的限流key还有1毫秒就过期,此时来了第一个请求,
- 如果你的需求是固定自然窗口限流(比如每秒的0时刻到下一秒的0时刻为一个窗口),该方案的窗口默认从每个窗口内第一个请求到达的时间开始算1秒,天然会跨两个自然窗口,导致相邻窗口的请求被累加到同一个计数器中,出现大面积误限流。
核心缺陷是:
EXPIRE NX只能保证过期时间被设置一次,无法保证当前计数器里的所有请求都属于同一个预期的限流窗口,跨窗口的计数累加就会导致限流逻辑不符合预期。
内容的提问来源于stack exchange,提问作者roim
相关产品推荐
相关产品推荐

