“延迟加载”不是缓存击穿的有效解法,它易被误解为让请求等待后再查db,实则会放大延迟、加剧超时;真正有效的是「异步加载+状态标记」或「互斥锁+非阻塞回源」,核心是只允许一个请求触发db查询,其余返回旧值或直查db不写缓存,避免锁竞争与等待。

不能直接用“延迟加载”解决缓存击穿,它既不是标准术语,也不对应任何可靠防护机制。真正起作用的是「异步加载 + 状态标记」或「互斥锁 + 非阻塞回源」,而所谓“延迟加载”容易让人误解为让请求等一会儿再查 DB——这反而会放大延迟、加剧超时。
什么叫“延迟加载”?它为什么不是解法
有些同学看到“后台刷新”“延后更新”就理解成:缓存失效后不立刻查 DB,而是等 100ms 再去查。这是危险操作:
-
Thread.sleep(100)会让所有未命中请求卡住,QPS 没降,但平均延迟翻倍甚至触发前端超时 - 它没解决根本问题——DB 仍会被同一波请求反复打,只是错开了时间点,可能造成 DB 负载毛刺更尖锐
- Redis 本身没有“延迟加载”命令或配置项,
EX、NX、GETEX都不提供该语义
真正有效的异步加载怎么做
核心是:只允许一个请求触发 DB 查询,其余请求要么返回旧值(stale-while-revalidate),要么直接走 DB 但不写缓存,避免锁竞争和等待。
- 用
SET key "loading" NX EX 5尝试设占位符,成功则投递异步任务(如线程池或 Kafka)去查 DB 并SET真实数据 - 失败则跳过锁等待,直接
SELECT数据库返回,**不写缓存**(防止脏数据覆盖) - 异步任务完成后再
DEL loading key,并确保写入时带新 TTL;若任务失败,占位符 5 秒后自动过期,避免死锁 - 业务层需接受“短暂 stale”——比如用户资料缓存允许 2 秒内不一致,就可返回本地内存中上一次的
user:10086值
为什么 Redisson 的 RLock 在这里容易翻车
很多人直接套用 RLock.tryLock(3, 10, TimeUnit.SECONDS) 包裹 DB 查询,结果线上抖动不断:
-
waitTime=3s太短 → 大量请求 fallback 到 DB,击穿没防住 -
leaseTime=10s若 DB 查询耗时 12s,锁提前释放 → 第二个线程又进 DB,重复加载 - 没做双重检查(double-check),即使拿到锁,也得再
GET一次缓存,否则可能白刷一次 DB - 没配自动续期(
watchdog)或续期失败兜底,锁在 GC 停顿时释放,引发并发写
最易被忽略的一点:占位符 key 和真实 key 必须用不同命名空间,比如 user:10086 和 user:10086:loading,否则 SET user:10086 "loading" NX 成功后,别的请求 GET user:10086 拿到字符串 "loading" 就懵了——它既不是数据,也不能当布尔值判断。











