缓存击穿是单个热点key过期瞬间,大量并发请求同时穿透缓存直击数据库同一行数据,表现为命中率断崖下跌、该key请求量突增数十倍、对应SQL高频低延迟执行、连接池活跃数瞬间拉满且堆栈集中于同一DAO方法。

缓存击穿导致数据库崩盘,核心特征是:单个热点 key 过期瞬间,大量并发请求同时穿透缓存,集中打到数据库同一行(或少数几行)数据上。它不像雪崩影响全站,也不像穿透针对无效数据,而是“精准爆破”——只压垮一个点,但这个点扛不住。
看监控:三秒内锁定击穿迹象
别急着翻代码,先盯住这三项实时指标:
-
Redis 命中率断崖下跌 + 某个 key 的 get 请求量突增数十倍:比如平时每秒查 10 次的
product:10086,突然飙到每秒 2000 次,而命中率从 99% 掉到 30%; -
数据库慢查询日志里,同一 SQL 出现高频、低延迟、高并发执行:例如
SELECT * FROM product WHERE id = 10086在 1 秒内被执行了 1500 次,平均耗时 8ms(说明不是 SQL 本身慢,是被反复刷); -
数据库连接池活跃数瞬间拉满,且堆栈集中在某一个 DAO 方法:线程堆栈里反复出现
ProductMapper.selectById或类似调用,说明流量卡死在重建这个 key 的逻辑上。
查日志:确认是否“过期+并发”双触发
重点检索应用日志中含以下关键词的组合:
- 缓存未命中日志(如
cache miss for key: product:10086)是否在极短时间内密集出现(例如 100ms 内打印 300 条); - 是否有对应数据库查询日志紧随其后(
query db for product 10086),且时间戳高度重合; - 检查该 key 的过期时间设置:是否为固定值(如统一
30 * 60秒),且写入时间集中在同一秒(比如批量导入商品后统一 setex),导致集体到期。
验代码:关键路径是否存在无保护回源
定位到该接口的缓存读取逻辑,检查三点:
- 是否对空值做了缓存(击穿不涉及空值,这点可略过);
- 是否在缓存未命中时直接查库并写回,中间没有任何锁或加载状态控制;
- 是否用了简单 if-else 回源方式(如
if (cache == null) { cache = loadFromDB(); setCache(); }),这是击穿温床——并发下会触发 N 次 DB 查询。
临时止血:三步快速降压
线上正在崩?优先保数据库:
- 立即对问题 key 手动刷新缓存:
redis-cli set product:10086 "{...}" EX 1800,绕过业务逻辑强行续命; - 在数据库侧对该 SQL 加轻量级限流(如 MySQL 的
max_execution_time=100),防全表扫描类误伤; - 若已引入 Sentinel/Hystrix,立刻触发该接口的熔断降级,返回预设兜底数据(如“商品信息加载中”),切断流量洪峰。
根治方案:让重建过程串行化
长期必须改代码,核心是“只允许一个线程去查库,其余等待结果”:
-
本地互斥锁(适合单机):用
synchronized或ReentrantLock包裹 DB 查询段,key 级粒度(如lock.lock(product:10086)); -
分布式锁(推荐):用 Redis 的
SETNX或 Redission 的RLock,加锁失败则 sleep 后重试,避免自旋消耗; -
逻辑优化(更优):对热点 key 设置永不过期(
EXPIRE改为后台定时异步刷新),或使用Redisson的getLock+tryLock组合实现带超时的懒加载。
击穿排查不靠猜,靠“命中率—SQL频次—日志时间戳”三角印证。找到那个被反复围攻的 key,就找到了故障原点。

















