redis穿透、击穿、雪崩

4 阅读5分钟

在高并发场景下,Redis 作为缓存层,主要目的是减轻数据库压力,提升系统响应速度。然而,如果使用不当,可能会出现三种典型的缓存问题:缓存穿透缓存击穿 和 缓存雪崩。下面分别解释它们的定义及应对策略。


一、缓存穿透

定义

缓存穿透指查询一个根本不存在的数据,缓存层和存储层都没有命中。由于缓存不命中,每次请求都会穿透到数据库,导致数据库压力骤增,甚至被恶意攻击利用。

典型场景

  • 用户查询一个不存在的商品ID。
  • 攻击者构造大量不存在的key发起请求。

应对策略

  1. 布隆过滤器
    在缓存前加一层布隆过滤器,将所有可能存在的数据哈希映射到位数组中。查询时先经过布隆过滤器,如果判定为不存在,则直接返回,不再访问数据库。
    优点:内存占用小,查询快。
    缺点:存在一定误判率(可能将不存在判定为存在),需定期重建过滤器。
  2. 缓存空对象
    当数据库查询结果为空时,也将这个空结果(如 null)缓存起来,并设置一个较短的过期时间(如几分钟)。这样后续相同请求直接从缓存返回,避免穿透到数据库。
    优点:实现简单,可解决大部分穿透问题。
    缺点:会占用少量缓存空间,若攻击者构造大量不同key,仍会缓存大量空对象。
  3. 接口层参数校验
    在入口层对请求参数进行合法性校验(如ID不能为负数、长度限制等),提前拦截非法请求,减少无效穿透。

二、缓存击穿

定义

缓存击穿指某个热点数据在缓存中过期失效的瞬间,恰好有大量并发请求同时访问该数据。由于缓存未命中,所有请求都打到数据库,导致数据库瞬时压力过大。

典型场景

  • 某个热门商品详情页的缓存过期,大量用户同时刷新导致数据库被击穿。

应对策略

  1. 互斥锁(Mutex Key)
    当缓存失效时,不是所有线程都去加载数据,而是只允许一个线程去数据库查询并重建缓存,其他线程等待。可以使用 Redis 的 SETNX 或 Redisson 等分布式锁实现。
    示例流程

    • 请求A发现缓存未命中,尝试获取锁。
    • 若获取成功,则去数据库加载数据并写入缓存,最后释放锁。
    • 若获取锁失败,则短暂休眠后重试,直到从缓存中获取数据。
  2. 逻辑过期(提前异步刷新)
    不设置物理过期时间,而是给数据添加一个逻辑过期时间字段。当查询时发现逻辑时间已过,则返回旧数据,同时异步去数据库加载新数据并更新缓存。这样可以保证请求始终能命中缓存,不会击穿数据库。

  3. 热点数据永不过期
    对于热点数据,可以设置物理上永不过期,但在后台定时或由更新事件触发刷新。结合互斥锁或异步更新,确保数据一致性。


三、缓存雪崩

定义

缓存雪崩指缓存层出现大规模失效或整体不可用,导致大量请求直接涌向数据库,造成数据库压力骤增甚至宕机。

常见原因

  • 大量缓存 key 在同一时间点过期(如设置了相同的过期时间)。
  • Redis 实例宕机,或集群不可用。

应对策略

1. 针对大量 key 同时过期

  • 过期时间随机化
    设置缓存过期时间时,在一个基础值上加上随机偏移量(如 5~10 分钟),避免大量 key 同时失效。
  • 热点数据分散过期
    对热点数据单独设计失效策略,比如错峰更新。
  • 多级缓存
    引入本地缓存(如 Caffeine、Guava Cache)作为一级缓存,Redis 作为二级缓存。本地缓存失效时,Redis 还能挡住一部分请求。

2. 针对 Redis 实例不可用

  • 高可用部署
    使用 Redis 主从 + 哨兵(Sentinel)或 Redis Cluster,保证单节点故障时能自动切换。
  • 限流 & 熔断
    在应用层使用限流组件(如 Sentinel、Hystrix)对数据库访问进行限流,防止大量请求同时击垮数据库。
  • 降级
    当 Redis 不可用时,返回默认值或静态数据,暂时牺牲一致性保证系统可用性。
  • 持久化 + 预热
    重启后,利用持久化文件(RDB/AOF)快速恢复缓存,并提前预热热点数据。

总结对比

问题原因核心应对
缓存穿透查询不存在的数据布隆过滤器、缓存空对象、参数校验
缓存击穿热点 key 过期瞬间高并发互斥锁、逻辑过期、永不过期
缓存雪崩大量 key 同时过期 / Redis 不可用过期随机、多级缓存、高可用、限流降级

在实际项目中,通常会组合使用多种策略来保障缓存层的稳定性,例如对核心热点数据采用“永不过期+异步刷新”,对一般数据采用“过期随机+互斥锁”,并在入口层统一做布隆过滤和参数校验。