加锁反致超时的根本原因是大量请求在缓存失效后排队等待分布式锁,叠加锁续期失败引发雪崩;应改用set nx占坑+降级兜底+本地缓存错峰。

缓存击穿时加锁,为什么反而超时?
根本原因不是锁本身慢,而是大量请求在 GET 缓存失败后,全部堵在 SETNX 或 RedissonLock.lock() 上排队等待——尤其当后端查询耗时长、锁过期时间又设得太短时,会出现“锁续期失败 → 锁提前释放 → 多个线程同时重建缓存 → 更多请求被阻塞”的雪崩循环。
用 SET key value EX seconds NX 替代手写分布式锁
Redis 原生命令已支持原子性“判断+设置”,比客户端加锁更轻量、无网络往返开销。关键点在于:用它做“占坑”,而不是做“互斥执行”:
-
SET cache:key "building" EX 30 NX成功 → 当前请求负责查库+回填缓存,其他请求直接跳过加锁逻辑 - 失败 → 不等锁,立刻走降级逻辑(如返回空/旧值/默认值),或最多重试 1–2 次
- 务必配合
EX设置合理过期时间(建议 ≥ 后端查询 P99 耗时 × 2),避免“占坑不建缓存”导致长期击穿
锁续期失败是超时主因,别依赖自动看门狗
Redisson 等框架的自动续期(watchdog)依赖心跳线程,一旦业务线程卡在 DB 查询里,心跳发不出,锁就过期。真实场景中常见于:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- MySQL 查询未加索引,单次耗时从 50ms 涨到 2s+
- JVM GC pause 超过锁过期时间(比如设了 10s 锁,但发生了一次 12s 的 Full GC)
- 锁 key 被误删(运维清缓存、脚本误操作)
解决思路:锁必须带明确业务超时控制,例如 lock.tryLock(3, 10, TimeUnit.SECONDS),第一个参数是获取锁最长等待时间,第二个是锁持有时间 —— 宁可抢不到锁快速失败,也不要无限等待。
兜底方案比锁更关键:本地缓存 + 过期时间随机化
单纯靠 Redis 分布式锁治标不治本。真正缓解击穿压力的是降低“所有请求都打到 Redis”的概率:
- 在应用层加一层
Caffeine或Guava Cache,设置 short TTL(如 1–2s)+ random jitter(比如 ±200ms),让请求有一定概率命中本地缓存,错开对 Redis 的集中冲击 - 缓存空对象时,
SET cache:key "" EX 60的过期时间不能和正常数据一致,否则会把空值也缓存太久;建议设为正常 TTL 的 1/5~1/3 - 异步重建缓存:抢到锁的线程同步返回旧值或默认值,另起线程查库并更新缓存,避免阻塞主请求流
击穿问题的复杂性不在锁怎么写,而在“锁该不该加、加了谁来承担失败成本、没抢到锁的请求该怎么活下来”。多数超时,其实是把缓存层当成了串行队列在用。










