Redis 热点 key 过期后数据库被打满:缓存击穿怎么排查和修复
来源:17golang原创
时间:2026-07-07 18:13:34 119浏览 收藏
不少 Redis 故障表面看是数据库响应变慢,实则根因出在缓存层突然失守。比如商品详情页的 product:1001 是热点 key,平时 Redis 命中率维持在很高水平,数据库负载一直很平稳;一旦某个时间点这个 key 刚好过期,大量同时涌来的请求直接没命中缓存,全量打到后端数据库。最终出现接口响应拖慢、DB QPS 瞬间冲高、Redis 命中率断崖式下跌,这就是非常典型的缓存击穿现场。
要点速览
- 缓存击穿通常发生在单个热点 key 过期的一瞬间,流量集中回源数据库。
- 排查时先看
TTL、缓存命中率、回源日志和数据库 QPS 是否同一时间突增。 - 修复时不要让所有请求都查数据库,常见做法是互斥锁、逻辑过期和短暂降级。
- 热点数据的过期时间要做随机化或主动续期,避免同一批 key 集中失效。
现象:Redis 没挂,数据库却突然被打满
缓存击穿最容易误导人的地方在于,Redis 本身可能完全正常。连接数没爆、内存没满、慢查询也没有明显堆积,但数据库 QPS 突然冲高,业务接口从平时的几十毫秒陡升到几秒级延迟。

第一步先确认是不是某个热点 key 在同一时间过期。可以从应用日志、缓存 miss 计数、数据库慢日志和 Redis 命中率几个维度交叉核对。如果只有某个商品、活动页、配置项或用户画像接口异常明显,热点 key 引发问题的嫌疑就很大。
redis-cli TTL product:1001 redis-cli --latency redis-cli INFO stats | grep keyspace
分层检查:先别急着扩容数据库
数据库被打满以后,很多人第一反应是加连接池、加只读库或者临时扩容。这里先别急。缓存击穿的核心问题是“太多请求同时回源”,如果不先控制回源流量,扩容只能把问题往后推,没过多久还是会被打穿。
| 检查项 | 证据 | 判断方向 |
|---|---|---|
| 热点 key 是否过期 | TTL product:1001 接近 0 或返回 -2 |
确认是不是同一 key 失效 |
| 缓存命中率是否下滑 | miss 计数短时间突增 | 判断是否有大量请求绕过缓存 |
| 数据库 QPS 是否同步冲高 | 查询同一条记录或同一类 SQL | 确认回源集中在热点数据 |
| 应用是否重复重建缓存 | 日志里多台机器同时查库并 set key | 说明缺少互斥保护 |
证据判断:看 miss、回源和同一条 SQL
如果日志里能看到大量类似 cache miss product:1001,数据库慢日志里又是同一条商品查询反复出现,基本可以确认问题不是“数据库突然变慢”,而是缓存没有挡住热点流量。
cache miss key=product:1001 db query product_id=1001 cost=438ms cache set key=product:1001 ttl=300
这个时候要重点看请求是否同时重建缓存。正常情况下,只有一个请求应该拿到重建资格,其他请求短暂等待、读取旧值或走降级。如果每个请求都去查库再 set 缓存,数据库压力会被瞬间放大数倍。
修复动作:用互斥锁控制回源
互斥锁的思路很直接:缓存未命中时,只有抢到锁的请求可以查数据库并重建缓存;其他请求可以等一小段时间后重试缓存,或者返回兜底数据。这样数据库只承受一次回源,而不是承受同一秒内所有请求。

lockKey := "lock:product:1001"
ok, _ := rdb.SetNX(ctx, lockKey, "1", 3*time.Second).Result()
if ok {
defer rdb.Del(ctx, lockKey)
data := loadProductFromDB(1001)
rdb.Set(ctx, "product:1001", data, 5*time.Minute)
return data
}
time.Sleep(50 * time.Millisecond)
cached := rdb.Get(ctx, "product:1001").Val()
这段代码只是说明实现思路,生产环境里还要处理锁超时、回源失败、空值缓存和重试次数。锁过期时间不能太长,也不能太短;太长会挡住正常恢复流程,太短可能导致多个请求又一起进数据库。
反向验证:命中率回升才算真的恢复
修复以后不要只看接口恢复正常就完事。至少要看四个指标:Redis 命中率是否回升,数据库 QPS 是否回落,互斥锁失败次数是否在可控范围,热点 key 的 TTL 是否分散。命中率回升、DB 压力下降,才说明缓存层重新挡住了流量。
redis-cli TTL product:1001 redis-cli INFO stats | grep keyspace redis-cli GET product:1001
如果命中率没有恢复,继续排查是不是 key 写错、序列化失败、TTL 太短,或者回源失败后没有写入缓存。这里别只盯着 Redis 本身,应用日志里的缓存 set 结果同样关键。
后续清单:热点 key 要提前治理
- 热点 key 的 TTL 加随机偏移,避免同一批 key 同时过期。
- 对核心热点数据使用逻辑过期,后台异步刷新,前台尽量读旧值。
- 缓存空值时设置短 TTL,避免不存在的数据反复打库。
- 给缓存 miss、回源耗时、锁等待次数和 DB QPS 加告警。
- 压测时模拟热点 key 过期,而不是只测缓存命中状态。
常见问题
缓存击穿和缓存雪崩有什么区别?
缓存击穿通常是单个或少数热点 key 失效,流量集中打到数据库;缓存雪崩更像大批 key 同时失效,影响面要大很多。
热点 key 永不过期是不是最简单?
不一定。永不过期能降低击穿风险,但会带来数据一致性问题。更稳妥的做法是逻辑过期、异步刷新或主动更新缓存。
互斥锁会不会让接口更慢?
会增加少量等待时间,但比所有请求同时查数据库更可控。锁等待时间要设置得很短,并配合旧值兜底或降级返回。
空值缓存能解决击穿吗?
空值缓存更适合防缓存穿透,比如查询不存在的数据场景。热点 key 过期导致的击穿,核心还是要控制回源并保护热点缓存。
-
200 收藏
-
312 收藏
-
117 收藏
-
426 收藏
-
171 收藏
-
280 收藏
-
数据库 · Redis | 1星期前 | Redis · 缓存治理 · Keyspace Notifications · 过期事件 · redis Pub/Sub Keyspace Notifications 过期事件 缓存监听 补偿任务181 收藏
-
501 收藏
-
400 收藏
-
313 收藏
-
235 收藏
-
464 收藏
-
436 收藏
-
407 收藏
-
数据库 · Redis | 3星期前 | Redis · Streams · 消费者组 · Pending · XACK · 消息堆积 消费者组 XACK XPENDING XAUTOCLAIM Redis Streams385 收藏
-
194 收藏
-
368 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习