Redis分布式锁超时必须覆盖完整回源链路耗时且留余量,过短导致重复回源,过长致宕机后锁残留;须用SET原子命令+唯一value+Lua校验解锁,禁用SETNX+EXPIRE非原子操作。

锁超时设太短:缓存未回源完成就释放,导致重复回源
Redis 分布式锁的 EXPIRE 时间不是“越短越安全”,而是必须覆盖「缓存失效 → 查库 → 写回缓存」整个链路的最大耗时。常见错误是直接拍脑袋设 1s 或 3s,结果在数据库慢查询、网络抖动、批量回源时,锁提前过期,多个请求同时穿透到后端,击穿数据库。
- 真实回源耗时得实测:用
time.Now()(Go)或System.nanoTime()(Java)包住整个回源逻辑,压测下取 P99 值,再加 20%~50% 余量 - 别忽略下游依赖波动:比如依赖的 MySQL 主从延迟突增、HTTP 调用第三方 API 超时重试,这些都会拉长回源路径
- 如果回源逻辑本身含异步或重试(如 3 次 HTTP 重试),锁超时必须包含全部重试总窗口,不能只算单次
锁超时设太长:节点宕机后锁长期残留,业务卡死
超时设过长看似“保险”,但一旦持有锁的服务进程崩溃或机器宕机,Redis 里这个锁会一直挂着,直到超时才自动释放——期间所有请求都被阻塞。这不是理论风险,是线上高频故障点。
- 建议上限参考服务平均重启时间:比如滚动发布耗时约 90s,那锁超时尽量不超过 120s,避免一次发布拖垮全链路
- 不要用固定大值(如 300s)兜底:它掩盖了回源慢的真实问题,且让故障恢复周期不可控
- 可配合主动续期(renew)机制,但注意:仅适用于「已知回源必然成功且耗时可预期」的场景;若回源中途失败,续期反而延长无效等待
用 SET key value EX seconds NX 替代 SETNX + EXPIRE
老写法先 SETNX 再 EXPIRE,两步非原子,存在锁设成功但过期没设上的竞态漏洞。Redis 2.6.12+ 的 SET 命令支持原子性设置值+过期+条件,这才是安全基线。
- 正确写法:
SET lock:goods:123 "8a4f5b" EX 10 NX,其中"8a4f5b"是唯一请求标识(如 UUID),用于后续解锁校验 - 错误写法:
SETNX lock:goods:123 "8a4f5b"+EXPIRE lock:goods:123 10,中间若进程 crash,锁永久存在 - 注意:
NX是必要条件,漏掉就会覆盖别人持有的锁,彻底失去互斥性
解锁必须校验 value,防止误删他人锁
解锁不是简单 DEL lock:key,否则 A 拿到锁但执行超时,B 又拿到新锁,A 结束后一删,把 B 的锁也干掉了——典型的“删错锁”事故。
- 解锁必须用 Lua 脚本保证原子性:
EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock:goods:123 8a4f5b - value 必须全局唯一且不可预测:用随机字符串(如
crypto/rand生成),别用时间戳或 PID,否则可能撞值 - 如果业务中存在“锁续期”,对应 value 也要同步更新,且续期命令同样要带 value 校验,否则续的可能是旧锁










