Redis 7.2 上用 INCR 加 EXPIRE 做每分钟 60 次的限流,上线第二天发现某个 key 的 TTL 是 -1,所有带这个 key 的请求一直被拒,重启服务也没恢复。

一开始怀疑是「主从切换时过期时间没同步过去」,查了一圈,方向不对。

排查方向其实有限,先把可能性排一排。INCR 和 EXPIRE 是两条命令,中间进程退出就会留下没有过期时间的计数器。也可能是只在 INCR 返回 1 时才设置过期,第一次漏掉就再也没有机会补。

另外,Lua 脚本里调了 redis.call 但没有把结果 return 给客户端。

先把可疑 key 的 TTL 和值一起打出来

redis-cli --scan --pattern 'rl:*' | ForEach-Object { redis-cli ttl $_ }

再往下改成 Lua 脚本把自增和过期放进一次执行

redis-cli EVAL "local c=redis.call('INCR',KEYS[1]);if c==1 then redis.call('EXPIRE',KEYS[1],ARGV[1]) end return c" 1 rl:u1 60

接着用 SET NX EX 初始化窗口,避免裸 INCR

redis-cli SET rl:u2 1 NX EX 60

剩下的就是加一条监控,TTL 为 -1 的限流 key 直接告警

redis-cli --scan --pattern 'rl:*' | Measure-Object

恢复的判定其实很直接。脚本执行后 rl:u1 的 TTL 落在 55 到 60 之间,计数到 60 后请求被拒,60 秒后自动恢复,线上不再出现 TTL 为 -1 的限流键。

限流的计数器和它的过期时间必须是一次原子操作,分开写就一定会有人在中途挂掉,Lua 是成本最低的修法。改动前把原配置备份一份,出问题能马上回滚,这比任何技巧都实用。

作者 9823189