Redis缓存击穿时,分布式锁导致死锁,如何有效解除?
- 内容介绍
- 文章标签
- 相关推荐
本文共计575个文字,预计阅读时间需要3分钟。
基本原因在于锁的释放逻辑和持有者不匹配,或者锁未设置过期时间。例如,使用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刷新过期时间,前提是该线程还活着
真正难处理的是锁被意外释放后业务还没结束,这种情况只能靠监控告警+人工介入,没有全自动兜底方案。
本文共计575个文字,预计阅读时间需要3分钟。
基本原因在于锁的释放逻辑和持有者不匹配,或者锁未设置过期时间。例如,使用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刷新过期时间,前提是该线程还活着
真正难处理的是锁被意外释放后业务还没结束,这种情况只能靠监控告警+人工介入,没有全自动兜底方案。

