返回博客
J
3D人偶 后端 · 缓存

高并发场景下 Redis 缓存三大经典问题与解决方案

JJokerChou 2026.07.05 约 8 分钟

缓存是提升系统吞吐、降低数据库压力的标配。但在高并发场景下,如果缓存设计不当, 它反而会成为系统的"阿喀琉斯之踵"。本文聚焦最经典的三个问题——缓存穿透、缓存击穿、缓存雪崩, 从成因到解法系统梳理,并给出可落地的代码思路(以 Java + Redis 为例)。

1缓存穿透(Cache Penetration)

是什么:查询一个根本不存在的数据,缓存和数据库都没有, 请求每次都"穿透"缓存、直击数据库。攻击者用不存在的 id(如负数、超大数据、随机串)高频请求, 就能用极低成本把数据库打垮。

解决方案

① 缓存空值(Cache Null):对查不到的数据也缓存一个空标记(如 NULL / ""),并设置较短 TTL。 这样同一 key 的后续请求直接命中缓存返回空,不再穿透到数据库。

// 缓存空值:防止不存在的 key 反复穿透到数据库
public String getWithCacheNull(String key) {
    String val = redis.get(key);
    if (val != null) {
        if (NULL_PLACEHOLDER.equals(val)) return null; // 命中空值标记
        return val;
    }
    val = db.query(key);                 if (val == null) {
        redis.setex(key, 60, NULL_PLACEHOLDER); // 空值缓存,短 TTL
        return null;
    }
    redis.setex(key, 3600, val);
    return val;
}
⚠️ 空值缓存的 TTL 要短,且同样建议加随机抖动,避免大量空 key 同时失效造成"次级雪崩"。

② 布隆过滤器(Bloom Filter):在缓存前加一层 BloomFilter,把所有可能存在的 key 预热进去。 请求先过 BF,不存在的 key 直接拦截,100% 挡掉穿透。这是拦截穿透最彻底的方案。

// 伪代码:布隆过滤器前置拦截
if (!bloomFilter.mightContain(key)) {
    return null; // 一定不存在,直接返回,不查缓存/库
}
// 可能存在,再走缓存 → 数据库流程

③ 接口层校验 + 限流:参数合法性校验(如 id > 0)、网关/Redis 维度限流,封掉恶意扫描与异常流量。

2缓存击穿(Cache Breakdown)

是什么:某个热点 key 在过期的瞬间,大量并发请求同时涌入,全部未命中缓存, 瞬间一起打到数据库,导致数据库被单点打挂。

🔑 与穿透的区别:击穿是"存在的热点 key"过期;穿透是"不存在的 key"。两者成因与解法都不同。

解决方案

① 互斥锁重建(分布式锁):只有一个线程去查库并重建缓存,其余线程等待重试或返回旧值。 用 Redis SET NX 实现锁;注意锁超时与误删(用 UUID + Lua 原子释放)。

// 互斥锁重建:同一时刻只有一个线程回源数据库
public String getWithMutex(String key) {
    String val = redis.get(key);
    if (val != null) return val;

    String lockKey = "lock:" + key;
    String uuid = UUID.randomUUID().toString();
    try {
        boolean locked = redis.set(lockKey, uuid, "NX", "EX", 30);
        if (locked) {
            val = db.query(key);          // 只有拿到锁的线程查库
            redis.setex(key, 3600, val);
            redis.eval(UNLOCK_LUA, lockKey, uuid); // Lua 原子释放
            return val;
        } else {
            Thread.sleep(50);            // 未拿到锁,短暂等待后重试
            return getWithMutex(key);
        }
    } catch (Exception e) {
        redis.eval(UNLOCK_LUA, lockKey, uuid);
        throw e;
    }
}

② 逻辑过期(Logical Expire):缓存中不设物理 TTL,而是存一个"逻辑过期时间"字段; 发现逻辑过期时,另起线程异步重建,当前请求先返回旧值。优点是不阻塞请求,缺点是返回的可能不是最新值(最终一致)。

③ 热点 key 永不过期 + 后台刷新:对极热点 key 不设过期,由后台定时任务或订阅数据变更主动更新,从根本上消除"过期瞬间"问题。

3缓存雪崩(Cache Avalanche)

是什么:大量缓存 key 在同一时间集中失效,或 Redis 实例宕机, 导致海量请求直击数据库,数据库瞬时压力过大甚至崩溃。

解决方案

① 过期时间加随机抖动TTL = base + random(0, jitter),把失效时间点打散,避免集体失效。

// 过期时间加随机抖动,避免批量 key 同时失效
int base = 3600;
int jitter = new Random().nextInt(600); // 0 ~ 600s 随机
redis.setex(key, base + jitter, value);

② 多级缓存:本地缓存(Caffeine / Guava)+ 分布式缓存(Redis)+ 回源,降低对 Redis 的单点依赖。

③ Redis 高可用:哨兵 / Cluster 部署,主从切换,避免单点故障引发整体雪崩。

④ 限流、降级、熔断:用 Sentinel / Hystrix 保护数据库,超时快速失败,返回默认或降级数据。

⑤ 缓存预热:大促前把热点数据提前加载进缓存,避免冷启动时的集中回源。

4三者对比一览

维度缓存穿透缓存击穿缓存雪崩
key 是否存在不存在存在(热点)存在(批量)
触发点查询不存在的数据单热点 key 过期瞬间大量 key 同时过期 / Redis 宕机
主要影响无效请求压垮数据库单点数据库被打挂数据库瞬时过载 / 崩溃
核心解法缓存空值、布隆过滤器、校验限流互斥锁、逻辑过期、热点不过期过期抖动、多级缓存、高可用、限流降级、预热

5最佳实践小结

缓存不是为了"加了就快",而是要在一致性、可用性、成本之间做权衡。 把穿透、击穿、雪崩这三座大山各自的解法落到架构里,缓存层才算真正"高可用"。

© 2026 JokerChou's Blog · 用 ❤️ 与 ☕ 制作 · 返回博客首页