接口限流跑了两天之后所有用户都被拦住,进 Redis 一看计数 key 停在阈值上,TTL 返回 -1,重启服务也降不下来,手动删掉才恢复。乍看像缓存或者权限的锅,追下去往往不是。
SET rate:uid:42 1 EX 60 NX # 返回 nil 说明窗口已存在,走下面这条 INCR rate:uid:42 TTL rate:uid:42
追下去是这么回事:先 INCR 再 EXPIRE 是两条独立的命令,中间进程重启或者执行出现异常,计数写进去了而过期时间没设上,这个 key 就变成了永久计数器,再怎么等也不会归零。
「Redis 限流计数」这个问题文档写得含糊,实际行为以当前版本为准,别照旧版本的结论套。一次只改一处,改完立刻验证,几个改动叠在一起,出问题就说不清是谁导致的。
先做最小复现,能稳定跑出来的问题,排查成本会低一个数量级。
具体做法是,两条命令合成一段 Lua 脚本,用脚本执行来保证原子性;或者先用 SET 命令的 NX 加 EX 参数建好时间窗口,再去做自增;上线之前巡检一遍所有没有过期时间的计数键,发现就补上。
验收就看一点,计数键都带上了 TTL,窗口结束之后自动归零,不再永久拦住用户。
