根本原因在于锁释放逻辑与持有者不匹配或未设过期时间:setnx+expire非原子导致孤儿锁;del无校验误删他人锁;过期时间不合理引发提前释放或长期阻塞;正确做法是用set key value ex seconds nx原子加锁、lua脚本校验value后删除、结合租约续期机制。

Redis分布式锁为什么容易死锁
根本原因在于锁的释放逻辑和持有者不匹配,或者锁没设置过期时间。比如用 SETNX + EXPIRE 两步操作时,SETNX 成功但进程崩溃或网络中断,EXPIRE 没执行,锁就永远卡在 Redis 里;又或者业务代码抛异常没走到 DEL,也会导致锁残留。
加锁必须带原子性+自动过期
避免手动分两步设锁和过期,直接用原子命令:
-
SET key value EX seconds NX(推荐)—— 一条命令完成「不存在才设值 + 设置过期」 - 不要用
SETNX后再EXPIRE,这两步非原子,中间出错就留孤儿锁 - value 必须是唯一标识(如 UUID 或服务实例 ID),不能写死成
"1",否则释放时无法校验归属
释放锁必须校验 ownership
只靠 DEL key 是危险的:A 拿到锁,B 在 A 还没释放时误删了它,后续并发就失控了。正确做法是用 Lua 脚本做原子校验:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
这个脚本确保只有持锁者才能删锁。对应 Java 工具类中 releaseLock(String key, String value) 的实现必须走这个逻辑,不能简单调 jedis.del(key)。
超时时间得比业务耗时长,但也不能太长
锁的 EX 值不是拍脑袋定的:
- 先压测单次数据库查询 + 缓存写入的 P99 耗时,比如是 800ms,那就至少设为 2000ms
- 但别设成 10 分钟——锁太久,一旦线程卡住,整个缓存重建流程就堵死几分钟
- 更稳妥的做法是「租约续期」:获取锁后启动一个后台心跳线程,定期用
GETSET刷新过期时间,前提是该线程还活着
真正难处理的是锁被意外释放后业务还没结束,这种情况只能靠监控告警+人工介入,没有全自动兜底方案。











