登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

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 突然冲高,业务接口从平时的几十毫秒陡升到几秒级延迟。

Redis 热点 key TTL 到期后缓存未命中导致数据库回源暴涨

第一步先确认是不是某个热点 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 缓存,数据库压力会被瞬间放大数倍。

修复动作:用互斥锁控制回源

互斥锁的思路很直接:缓存未命中时,只有抢到锁的请求可以查数据库并重建缓存;其他请求可以等一小段时间后重试缓存,或者返回兜底数据。这样数据库只承受一次回源,而不是承受同一秒内所有请求。

Redis 缓存击穿通过互斥锁回填缓存并让数据库压力恢复正常

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 过期导致的击穿,核心还是要控制回源并保护热点缓存。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>