Redis缓存击穿时,分布式锁导致死锁,如何有效解除?

2026-05-07 15:561阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计575个文字,预计阅读时间需要3分钟。

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 刷新过期时间,前提是该线程还活着

真正难处理的是锁被意外释放后业务还没结束,这种情况只能靠监控告警+人工介入,没有全自动兜底方案。

标签:Redisred

本文共计575个文字,预计阅读时间需要3分钟。

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 刷新过期时间,前提是该线程还活着

真正难处理的是锁被意外释放后业务还没结束,这种情况只能靠监控告警+人工介入,没有全自动兜底方案。

标签:Redisred