一文彻底搞懂 Redis 缓存穿透、击穿、雪崩与熔断机制

本文将以实际开发中最常见的缓存异常问题为核心,系统梳理三大缓存问题:穿透、击穿、雪崩,并引入熔断降级机制,帮助你构建高可用、高容错的缓存体系,适用于电商、门户、大数据接口等高并发场景。


一、背景与引子

在大规模系统架构中,Redis 被广泛用作高性能缓存中间件,承担着请求的第一道防线。然而随着业务量增长,单纯的缓存并不能解决所有问题,反而在某些场景下变成了隐形炸弹。如果某个设计疏忽,可能引发连锁反应,导致:

  • Redis 被击穿 → DB 瞬间崩溃

  • 缓存雪崩 → 所有请求命中数据库,服务不可用

  • 用户大量访问非法数据 → Redis / DB 双重压力

  • Redis 宕机 → 无熔断策略,系统直接返回 500

这些问题归根结底可以划分为三大核心问题 + 一种防御机制

穿透、击穿、雪崩 + 熔断

接下来逐个深入分析。


二、缓存穿透(Cache Penetration)

2.1 什么是缓存穿透?

缓存穿透指的是用户请求的数据既不在缓存中,也不在数据库中,因此每次请求都会落到数据库。

场景举例:
  • 攻击者发送大量随机 userId、productId

  • 普通用户请求一个已被删除或不存在的 ID

  • 接口参数未校验,允许传递负数、空串等非法值

2.2 结果影响

  • Redis 无命中

  • 数据库压力倍增

  • 可能被恶意刷崩(典型 DDoS)

2.3 解决方案

✅ 缓存空值
if (value == null) {
    redisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);
    return null;
}
  • 建议设置较短过期时间(如 5分钟)

  • 注意不能对所有业务都缓存 null,否则缓存污染

✅ 参数校验拦截
if (id <= 0) {
    return null; // 拦截非法参数
}
  • 常用于控制层或网关层提前校验参数合法性

✅ 布隆过滤器(BloomFilter)

用于拦截不存在的 key,避免每次都访问 Redis/DB。

BloomFilter<Long> filter = BloomFilter.create(Funnels.longFunnel(), 1_000_000);
filter.put(1L); // 初始化已有 ID
if (!filter.mightContain(id)) return null;
  • 场景:商品ID、用户ID大规模数据量初始化

  • Guava 或 Redisson 都支持布隆过滤器


三、缓存击穿(Cache Breakdown)

3.1 什么是缓存击穿?

某个高并发热点 key在某一刻过期,造成大量请求同时落入数据库。

3.2 场景复现

比如“秒杀详情页”、“爆款商品页”,一个 key 的 TTL 正好到期时,Redis 失效,大量请求涌入 DB。

3.3 解决方案

✅ 互斥锁方案(mutex)
Boolean isLock = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.FALSE.equals(isLock)) {
    // 自旋重试
    Thread.sleep(50);
    return getFromCache(id);
}
try {
    String dbData = queryDb(id);
    redisTemplate.opsForValue().set(key, dbData, 30, TimeUnit.MINUTES);
} finally {
    redisTemplate.delete(lockKey);
}
  • Redisson 提供更安全分布式锁

  • 自旋+延迟策略防止请求风暴

✅ 逻辑过期(推荐)

数据未真正失效,但后台自动更新。

@Data
class RedisData {
    private Object data;
    private LocalDateTime expireTime;
}
  • 请求先判断 expireTime 是否过期

  • 过期后开启线程池异步更新,不影响当前请求响应

if (redisData.getExpireTime().isBefore(LocalDateTime.now())) {
    threadPool.submit(() -> rebuildCache(id));
}
✅ 异步队列 + 延迟双删(高并发)
  • 先删缓存 → 异步延迟删除 → 再写缓存

  • 延迟补偿机制防止写缓存被其他线程“脏读”


四、缓存雪崩(Cache Avalanche)

4.1 什么是缓存雪崩?

在同一时间内,大量缓存 key 同时失效,导致数据库雪崩式崩溃。

4.2 场景

  • 系统重启时未做预热

  • 缓存 TTL 设置为同一时间段

  • Redis 整体宕机

4.3 解决方案

✅ 给 key 设置随机 TTL
int base = 60 * 30;
int offset = new Random().nextInt(60 * 10);
redisTemplate.opsForValue().set(key, value, base + offset, TimeUnit.SECONDS);
  • 避免大规模 key 同时过期

✅ 数据预热
  • 启动时从 DB 将热点数据 load 到 Redis

  • 可使用初始化脚本/定时任务完成

✅ 本地缓存兜底(Caffeine)
LoadingCache<String, Object> cache = Caffeine.newBuilder()
        .expireAfterWrite(10, TimeUnit.MINUTES)
        .build(this::queryDb);

小型数据量、高访问频率的接口,可加 JVM 层缓存兜底

✅ Redis 集群 + Sentinel 监控
  • 使用哨兵或 Redis Cluster 提供高可用

  • 若主节点挂掉可自动 failover


五、熔断与降级策略

5.1 为什么要做熔断?

一旦 Redis 故障,或者响应超时,系统没有任何兜底手段将会直接崩溃。

5.2 实践场景

  • Redis 无法连接或响应慢

  • 网络波动引发访问阻塞

  • 不做隔离 → 主业务模块被 Redis 拖死

5.3 解决方案

✅ Redis 操作加异常捕获
try {
    String data = redis.get(key);
    return data != null ? data : fallback();
} catch (Exception e) {
    return fallback(); // 降级返回
}
✅ 限流熔断框架集成(Sentinel / Resilience4j)
  • 设置失败比例阈值(如 50%)

  • 设置半开恢复窗口(如 30 秒后尝试恢复)

  • 可以对 Redis 封装成独立接口调用

✅ fallback 逻辑建议
  • 对低一致性要求的接口返回兜底数据

  • 高一致性建议返回“稍后再试”


六、总结与最佳实践建议

问题触发条件解决方式实践建议
穿透数据不存在布隆过滤器、空值缓存非法参数校验、定期清理 null
击穿热点数据过期加锁/逻辑过期热点数据隔离设计
雪崩大量缓存同时失效TTL 随机化、本地缓存、预热Redis 分区集群化部署
熔断Redis 宕机/慢响应异常捕获+fallback配合限流框架自恢复

七、结语:稳定性不是靠补丁,是靠设计

稳定性架构从来不是“问题发生后再补”,而是提前设计、预估极限、落地容灾方案。
缓存设计如果缺乏熔断/降级思维,可能成为系统不可承受之痛。

建议构建自己的:

  • 缓存工具类封装统一接口

  • fallback 降级策略结合业务要求

  • 异步更新/延迟双删机制集成线程池

  • Redis 状态监控告警机制(配合 Prometheus、Grafana)

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐