缓存击穿是单个key过期瞬间遭遇高并发查询,表现为数据库同一SQL密集执行、Redis某key miss_rate短时超95%、线程池阻塞;需通过标记过期时间+分布式锁+动态锁时长+Lua安全释放+失败重试监控实现精准识别与防护。

如何识别正在发生的缓存击穿
缓存击穿不是“偶尔查不到”,而是某个 key 在过期瞬间遭遇高并发请求,大量线程同时发现缓存为空,又几乎同时去查库。典型现象包括:数据库慢查询日志里突然出现同一 SQL 的密集执行、Redis 监控中某 key 的 miss_rate 短时飙升至 95%+、应用线程池活跃数暴涨且大量线程阻塞在数据库连接上。
关键判断点是:是否集中在单个 key(比如 "product:10086"),而不是全量缓存失效。如果是全量,大概率是缓存雪崩,得换思路。
用 Redis 命令 + 应用层埋点做热点 Key 实时识别
别依赖事后告警——要在击穿发生前或刚冒头时捕获。核心是两件事:统计访问频次 + 判断是否临近过期。
- 对高风险业务 key(如商品、订单、用户主页)在写缓存时,统一加一个标记字段,例如
"product:10086:expire_ts",存入逻辑过期时间戳 - 在读缓存逻辑中,先
GET主 key,再GET对应的:expire_ts,如果当前时间距该时间戳不足 30 秒,就主动触发tryLock("lock:product:10086") - 不推荐用
KEYS扫全量 key 做统计,会阻塞 Redis;改用SCAN配合客户端采样,或直接开启 Redis 的slowlog和latency monitor抓异常调用模式
自动加锁不能只靠 setIfAbsent
setIfAbsent 是基础,但生产环境必须补三处:
- 锁 key 必须带业务前缀和唯一标识,比如
"lock:hot:product:10086",避免不同服务误删彼此的锁 - 锁的过期时间不能硬写 10 秒,要根据对应数据库查询 + 缓存写入的 P99 耗时动态算,建议设为
max(10, db_p99 * 2)秒 - 释放锁必须用 Lua 脚本校验 value 是否匹配,否则可能 A 线程锁超时被自动释放后,B 线程还没执行完就误删了 A 写的缓存 —— 这类脚本必须原子执行:
EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock:hot:product:10086 abc123
监控与降级必须联动,不能只盯 Redis
光看 Redis 的 hit_rate 没用。真正要监控的是“尝试加锁失败后退避重试”的次数,以及“返回逻辑过期数据”的比例。
当 lock:hot:product:10086 的失败重试次数在 1 分钟内超过 50 次,或逻辑过期数据返回占比 > 30%,就得自动触发降级:比如把该 key 的缓存策略临时切为永不过期,或直接返回兜底静态页。这些动作不能靠人盯,得用 Prometheus + Alertmanager + 自研 Operator 自动执行。
最容易被忽略的是:逻辑过期方案下,后台异步刷新线程失败时,没有任何 fallback。你得确保这个线程有独立的健康检查和失败重投机制,否则“永远不过期”就真成“永远不更新”了。











