Redis

Redis 缓存穿透、击穿、雪崩完整解决方案

·11 分钟阅读·4140 字

缓存穿透、缓存击穿、缓存雪崩的成因与解决方案——布隆过滤器、互斥锁、缓存预热、降级限流

📋 目录

Redis 缓存穿透、击穿、雪崩完整解决方案

一、缓存穿透

问题回顾

查询缓存、数据库都不存在的数据,请求直达MySQL,恶意/非法请求会持续压垮库。

方案1:接口层参数校验(前置拦截,首选)

原理

在网关/Controller 层提前过滤非法请求,不让无效请求进入缓存与数据库。

落地规则

  1. 基础格式校验:ID 必须大于0、手机号/邮箱正则校验、分页参数合法性校验;
  2. 黑名单拦截:拦截高频恶意IP、异常账号;
  3. 简单示例(伪代码)
// 商品ID必须为正整数
if (goodsId <= 0) {
    return Result.fail("参数非法");
}

优缺点

  • 优点:实现简单、零额外开销,从源头拦截;
  • 缺点:仅能拦截格式错误,无法拦截合法但不存在的正常参数。

方案2:缓存空值 / 缓存占位符(业务通用,成本低)

原理

数据库查询结果为空时,主动往 Redis 写入空标识,并设置较短过期时间。后续相同请求直接返回,不再查库。

实操步骤

  1. 查 Redis,存在则直接返回;
  2. Redis 不存在 → 查询 MySQL;
  3. MySQL 无数据:SET key "" EX 300(空值,过期5分钟);
  4. MySQL 有数据:正常写入缓存并返回。

Redis 命令示例

# 数据不存在,缓存空值,5分钟过期
SET goods:-999 "" EX 300

优缺点

  • 优点:开发简单、兼容所有业务,中小项目首选;
  • 缺点:
  1. 大量不存在的Key会占用Redis内存;
  2. 若恶意批量生成不同无效Key,仍会引发穿透。

方案3:布隆过滤器(高并发大流量首选)

原理

提前把数据库所有合法主键/业务Key加载到布隆过滤器。请求进来先校验过滤器:

  • 过滤器判断不存在 → 直接拦截,不查Redis、MySQL;
  • 过滤器判断存在 → 正常走缓存+数据库流程。

落地流程

  1. 项目启动/定时任务:全量DB数据,将主键写入布隆过滤器;
  2. 新增数据:同步写入布隆过滤器;
  3. 请求链路:接口 → 布隆过滤器 → Redis → MySQL。

核心特性&坑点

  1. 存在误判:过滤器说“存在”,数据不一定真存在;但说“不存在”,一定不存在;
  2. 不支持删除:传统布隆过滤器无法单独删除某一条数据,数据删减场景需用计数布隆过滤器
  3. 适合场景:数据固定、主键明确(商品ID、用户ID)的高并发系统。

优缺点

  • 优点:内存占用极小、拦截效率极高,抵御大规模恶意穿透;
  • 缺点:有误判、不支持删除、需要维护数据同步,复杂度高。

方案4:接口限流 & 封禁(防恶意攻击)

原理

对单个IP/账号单位时间内的请求做频率限制,高频异常请求直接封禁。

实现

网关层(Nginx/Spring Cloud Gateway)+ Redis 计数器限流,短时间大量查询不存在数据则拉黑IP。


二、缓存击穿

问题回顾

单个超高并发热点Key 正常存在,因TTL过期,瞬间海量并发同时击穿到数据库。

方案1:分布式互斥锁(通用方案,保证单线程查库)

原理

Key 失效后,只允许一个请求去查询数据库并更新缓存,其他请求等待或重试,避免并发打库。

Redis 实现(推荐 SET NX PX 原子命令)

# NX:不存在才创建(互斥);PX:锁超时时间(防止死锁)
SET lock:goods:123 random_str NX PX 5000

完整执行流程

  1. 客户端查询热点Key,Redis 不存在;
  2. 尝试获取分布式锁:
  • 获取成功 → 查询MySQL → 写入Redis → 释放锁;
  • 获取失败 → 休眠短暂时间后重试查询缓存;
  1. 锁设置超时时间,避免服务宕机导致死锁。

伪代码流程

public Goods getGoods(Long id) {
    String key = "goods:" + id;
    String lockKey = "lock:goods:" + id;
    // 1.查缓存
    String cache = redis.get(key);
    if (cache != null) {
        return parse(cache);
    }
    // 2.尝试加锁
    boolean lock = redis.set(lockKey, uuid, "NX", "PX", 5000);
    if (lock) {
        try {
            // 3.查数据库
            Goods goods = db.query(id);
            // 4.写入缓存
            redis.set(key, JSON.toJSONString(goods), 3600);
            return goods;
        } finally {
            // 5.释放锁
            redis.del(lockKey);
        }
    } else {
        // 6.加锁失败,短暂休眠后重试
        Thread.sleep(100);
        return getGoods(id);
    }
}

优缺点

  • 优点:实现简单、数据一致性强,适配绝大多数场景;
  • 缺点:高并发下会有少量请求等待,轻微影响吞吐量。

方案2:逻辑过期(热点商品首选,无等待、高可用)

原理

Redis 中 Key 永久不删除(不设置TTL),在 Value 内部存储逻辑过期时间

  1. 查询时先判断逻辑时间:未过期 → 直接返回数据;
  2. 已逻辑过期:异步线程更新缓存,当前请求直接返回旧数据,无阻塞、无击穿。

数据结构设计

{
  "data": { "id":123, "name":"商品" },
  "expireTime": 1789000000000  // 逻辑过期时间戳
}

执行流程

  1. 查询Redis,数据存在;
  2. 判断逻辑时间:未过期 → 直接返回;
  3. 已过期:通过互斥锁控制只有一个异步线程更新缓存,主线程返回旧数据;
  4. 用户始终有数据返回,不会打到数据库。

优缺点

  • 优点:性能极高、无并发等待,完美解决热点Key击穿;
  • 缺点:存在数据短暂不一致(容忍旧数据),金融、支付等强一致性场景禁用。

方案3:热点Key 永不过期(最简方案)

原理

对秒杀、首页、活动配置等绝对热点Key,不设置过期时间,由人工/定时任务主动更新缓存。

适用场景

运营可控、变更频率低的热点数据(首页轮播、活动规则)。

优缺点

  • 优点:零代码改造、彻底杜绝击穿;
  • 缺点:数据更新不及时,需额外维护更新逻辑。

三、缓存雪崩

问题回顾

两类诱因:① 大量Key集中过期;② Redis集群宕机/不可用。大面积缓存失效,请求批量压垮数据库。

场景一:大量Key集中过期 解决方案

方案1:过期时间随机打散(最基础、必做)

原理

在原有TTL基础上,叠加随机时间,错开批量过期峰值。

示例

原过期时间 1小时:

// 基础3600秒 + 0~600秒随机值
int ttl = 3600 + new Random().nextInt(600);
redis.set(key, value, ttl);

优缺点

  • 优点:一行代码改造,成本最低,缓解集中过期;
  • 缺点:只能缓解,无法彻底杜绝,需配合其他方案。

方案2:分层TTL 设计

核心业务、非核心业务使用不同过期档位,避免全域Key同时失效。


场景二:Redis 服务宕机/集群不可用 解决方案

方案1:Redis 高可用架构(基础保障)

  1. 主从 + 哨兵:主节点宕机,哨兵自动选从库升级为主节点,故障自动转移;
  2. Redis Cluster 集群:分片+主从,部分节点故障不影响整体服务。

核心:避免单机单点故障。


方案2:多级缓存(本地缓存 + 分布式缓存)

原理

架构叠加 JVM本地缓存(Caffeine/Guava) + Redis 两层缓存。

  1. 请求优先查本地缓存;
  2. 本地无 → 查Redis;
  3. Redis宕机,本地缓存依然兜底,请求不会直达数据库。

架构链路

请求 → 本地Caffeine缓存 → Redis → MySQL

优缺点

  • 优点:大幅提升容错性、减轻Redis压力;
  • 缺点:本地缓存存在数据不一致,集群节点数据不同步。

方案3:服务限流、熔断、降级(兜底防线)

基于网关/服务框架实现,Redis全面不可用时启动保护:

  1. 限流:限制数据库总请求量,防止压垮;
  2. 熔断:检测到MySQL压力过大,直接断开对DB的访问;
  3. 降级:返回默认兜底数据、静态页面、提示文案,放弃非核心业务。
    常用组件:Sentinel、Hystrix、Gateway。

方案4:搭建异地/多机房缓存集群

超大型项目,做多机房Redis容灾,单机房故障切换至备用机房。


四、方案选型总结(生产落地建议)

1. 缓存穿透 选型优先级

  1. 普通业务:参数校验 + 缓存空值(性价比最高);
  2. 高并发/防恶意攻击:参数校验 + 布隆过滤器 + 限流

2. 缓存击穿 选型优先级

  1. 强数据一致性:分布式锁
  2. 高并发热点、允许短暂不一致:逻辑过期
  3. 低变更热点配置:永不过期

3. 缓存雪崩 选型优先级

  1. 预防集中过期:TTL随机打散(所有项目必加);
  2. 防Redis宕机:主从/Cluster高可用 + 多级缓存
  3. 最终兜底:限流+熔断+降级

五、面试标准作答(精简版)

  1. 缓存穿透:查询不存在数据,请求直达DB。处理:接口参数校验、缓存空值、布隆过滤器、IP限流。
  2. 缓存击穿:单个热点Key过期,并发打库。处理:分布式互斥锁、逻辑过期、热点Key不设过期。
  3. 缓存雪崩:批量Key集中过期或Redis宕机。处理:过期时间加随机值、Redis高可用、本地多级缓存、服务限流熔断降级。

关联文档