Redis缓存击穿:热点Key过期打崩数据库的3种解决方案

Redis 缓存击穿:热点 Key 过期打崩数据库的 3 种解决方案

缓存击穿、缓存穿透、缓存雪崩经常被混淆。本文用一个「首页热点配置」的真实故障,讲清楚三者的区别,重点给出缓存击穿的 3 种解决方案(互斥锁 / 逻辑过期 / 异步重建),并附可直接使用的代码。

一、问题案例

首页有一个「今日推荐」接口,查一个配置对象,QPS 数千。为了抗压,我给这个配置加了 Redis 缓存,过期时间 10 分钟。

上线第二天晚上,数据库连接数突然打满,首页接口大面积超时。监控显示:DB 的 QPS 从几百瞬间飙到几万,持续几秒又回落。

排查后发现,出问题的只有「今日推荐」这一个 key------它在过期的那一秒,几千个请求同时发现缓存没了,全部穿透到数据库。这不是雪崩,是击穿。

二、原理详解:击穿、穿透、雪崩的区别

类型 触发点 范围 典型场景
击穿 单个热点 key 过期瞬间被并发穿透 单个 key 热门商品、首页配置
穿透 查询一个根本不存在的 key 单个 key 恶意请求、不存在的 id
雪崩 大量 key 同时过期,或 Redis 宕机 全局 过期时间没打散、缓存层故障

击穿的关键特征:热点 key + 过期瞬间 + 高并发。三者缺一不可。普通 key 过期被穿透,压力不大;热点 key 但没并发,也不会打崩。

三、实战代码:3 种解决方案

方案一:互斥锁(最常用)

缓存未命中时,只让一个请求去查库并重建缓存,其余请求等待:

java 复制代码
public String getConfig() {
    String v = redis.get("config:today");
    if (v != null) return v;

    // 只有一个请求能拿到锁去查库重建
    boolean locked = redis.setIfAbsent("config:today:lock", "1", 3, TimeUnit.SECONDS);
    if (locked) {
        try {
            v = db.query();
            redis.set("config:today", v, 10, TimeUnit.MINUTES);
        } finally {
            redis.delete("config:today:lock");
        }
    } else {
        // 拿不到锁,稍等后重查一次缓存
        Thread.sleep(50);
        v = redis.get("config:today");
    }
    return v;
}

适用:数据强一致、允许短暂等待。缺点:拿锁失败的请求要重试,代码稍繁琐。

方案二:逻辑过期(不物理删除 key)

key 永不物理删除,值里存一个「逻辑过期时间戳」。读到已逻辑过期的值,先返回旧值顶着,后台异步重建:

java 复制代码
class ConfigData {
    String value;
    long expireAt; // 逻辑过期时间戳
}

public String getConfig() {
    ConfigData data = redis.get("config:today");
    if (data == null) return rebuildWithLock();
    if (System.currentTimeMillis() < data.expireAt) return data.value;

    // 已逻辑过期:先返回旧值,异步重建
    asyncRebuild();
    return data.value;
}

适用:热点数据、对实时性不敏感。优点:用户永远不等待;缺点:会短暂返回旧值。

方案三:永不过期 + 后台定时刷新

热点 key 直接不设过期时间,用一个定时任务或消息队列,在数据变更时主动更新缓存。适合「数据量小、变更少、必须强一致」的配置类场景。

四、延伸:顺带防穿透

穿透(查不存在的 key)的防法:

  1. 空值缓存:把「不存在」也缓存起来(短过期时间),避免每次都打库。
  2. 布隆过滤器:在缓存前用布隆过滤器拦截不存在的 key。

五、总结

  • 击穿、穿透、雪崩是三种不同的问题,先分清再下手:击穿是"一个热点 key 过期",穿透是"查不存在的 key",雪崩是"一大片同时失效或缓存挂了"。
  • 击穿首选互斥锁(简单可靠),热点数据可用逻辑过期(不卡用户)。
  • 顺手把空值缓存或布隆过滤器补上,挡住穿透。

我是无羡(小剑),全栈偏后端的独立开发者。

作品集:无羡 · 独立开发者作品集

如果对你有帮助,欢迎点赞、收藏、关注。

相关推荐
code_slave(码畜)6 分钟前
微服务架构落地:慢 SQL 治理与连接池调优 —— 守住数据库的最后一道防线
数据库·spring boot·sql·spring cloud·微服务·架构
海棠Flower未眠9 分钟前
下单请求处理流程:商品维度分布式锁与 Redis 预扣库存
redis·分布式·junit
我不会起名字32211 分钟前
Redis 从 0 到 1(二十一):P99 涨到 80ms,慢查询日志 5 步定位法 + Go 采集器
redis·go·慢查询·排查·slowlog
挖掘狂人12 分钟前
Redis 为什么快?把 "存内存" 这句话拆开看
redis·后端·性能优化
IvorySQL22 分钟前
PostgreSQL 日报|子表 CHECK 约束恢复标记错误(11 月 2 日)
数据库·postgresql
codigger44 分钟前
Redis 为什么快?把 "存内存" 这句话拆开看
redis·后端·性能优化·程序员
我不会起名字32244 分钟前
Redis 从 0 到 1(十八):List、Pub/Sub、Stream 为什么生产只用 Stream?Go 实操
redis·消息队列·go·stream·pub/sub
你看我名字1 小时前
Redis 高可用、持久化与故障切换:从缓存组件到云上基础设施
数据库·redis·缓存