缓存击穿死锁核心是业务层锁使用不当或兜底缺失,非redis底层问题;需检查加锁超时、解锁原子性、业务耗时,并配看门狗与全局超时兜底。

排查缓存击穿时多进程并发回源导致的死锁,核心不是“Redis自己卡住了”,而是业务逻辑在加锁、查库、写缓存这一整条链路上出现了协作失败。死锁本身极少由Redis底层引发,几乎都源于分布式锁使用不当或业务兜底缺失。
看监控:先确认是不是真死锁,还是单纯阻塞
死锁和高竞争阻塞表现相似,但本质不同。先快速区分:
- Redis层面无明显CPU或内存异常,connected_clients持续高位不降,大量客户端连接停留在WAIT状态(可通过
CLIENT LIST查client类型和idle时间) - 应用日志里反复出现“获取锁超时”“重试N次失败”“等待锁超过X秒”,但没有成功执行查库+回填的完整日志
- 数据库QPS极低甚至为0,说明请求根本没走到DB——它们全卡在锁等待环节,不是在打库
- 用
redis-cli --scan --pattern "lock:*"检查锁key是否长期存在(比如超2分钟还没被删),再用PTTL lock:xxx确认是否已过期;若TTL仍为正数且长时间不变,大概率是持有锁的进程崩溃后未释放
查代码:重点盯三处锁生命周期漏洞
绝大多数“伪死锁”其实源于锁没被正确释放,而非环形等待。重点关注以下逻辑:
-
加锁没配超时(EX/PX):只用
SETNX不设过期时间,一旦进程宕机,锁永久残留。必须用SET key value NX PX 5000这类原子命令 -
解锁非原子操作:先GET再DEL,中间若进程挂掉,锁就漏删了。正确做法是用Lua脚本保证“判断+删除”原子性:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end - 业务逻辑里有不可中断的长耗时操作:比如查库后做了复杂JSON序列化、调了外部HTTP接口、或者用了无超时的IO操作,导致锁持有时间远超预期,其他线程无限等待
验部署:Redlock不是万能解药,别迷信节点数量
用Redlock也不代表安全。常见踩坑点:
- 5个Redis实例全跑在同一台物理机或K8s同一Node上,节点故障是批量发生的,多数派瞬间失效,锁协调机制归零
- 客户端加锁时设置了30秒leaseTime,但业务处理实际花了45秒,锁自动过期后,另一个线程拿到锁又开始查库——此时两个线程同时操作同一份数据,可能造成缓存覆盖或DB重复查询,这不是死锁,但更危险
- 没启用看门狗(Watchdog)机制:Redisson等客户端支持自动续期,但手动实现时若忘了心跳续期,锁会在业务中途过期,引发并发回源
做兜底:不让一个key拖垮整个服务
即使锁机制出问题,系统也该有退路:
- 所有缓存回源操作必须带全局超时(如
CompletableFuture.orTimeout(3, TimeUnit.SECONDS)),超时直接返回空或默认值,不重试 - 对热点key单独配置熔断策略:比如1分钟内连续5次锁获取失败,自动降级为“本地缓存+短TTL”,避免全量请求堵死
- 在Redis中为锁key设置
maxmemory-policy volatile-lru,防止锁堆积占满内存;同时用SCAN定期清理过期锁残留











