缓存穿透、缓存击穿、缓存雪崩:现象及解决方案

缓存工作机制:用户发起请求,先查找 Redis 中的缓存。

  • 若 Redis 没有缓存相应记录,则将请求发送给 SQL 数据库进行查询。Redis 接收到 SQL 数据库的返回结果后进行缓存,并返回给用户。
  • 若 Redis 中有缓存记录,则直接返回。

1、缓存穿透

缓存穿透访问的 Key 不存在

  • 查询缓存(Redis)时无法找到指定 Key,因此会请求查询数据库
  • 实际上,缓存与数据库中都不能命中该 Key。
  • 利用一个不存在的 Key 值进行攻击,会穿透数据库。

解决方案

  1. 对空值缓存:若查询返回的数据为 null,将结果 null 进行缓存并设置较短的过期时间(通常不超过 5min)
  2. 设置白名单:使用 bitmaps 定义一个可访问名单。
  • 名单 ID 作为 bitmaps 的偏移量。
  • 每次访问数据时,将访问者 ID 与 bitmaps 中的 ID 进行比较。
  • 若访问者 ID 不在 bitmaps 中则进行拦截。
  1. 布隆过滤器:哈希函数 + 位图
  • 做法:将所有可能存在的数据哈希到一个足够大的 bitmaps 中,请求访问之前会判断过滤器中是否有该 Key,不存在的数据时会被 bitmaps 拦截。
  • 特点:空间效率和查询效率快,但有一定的误识别率和删除困难。
  1. 实时监控:当 Redis 的命中率骤低,需排查访问对象和访问数据,可设置黑名单限制服务。

2、缓存击穿

缓存击穿大量并发请求同时访问某个缓存过期的 Key

  • Key 对应的数据存在,但在 Redis 中缓存过期。
  • 此时有大量并发请求数据库访问压力瞬间增大,则 Redis 会从数据库加载大量数据并缓存。
  • 数据库的访问压力瞬间增大。

解决

  • 预先设置热门数据:在 Redis 高峰访问前,将热门数据提前存入 Redis 中,增加热门数据 Key 的时长。
  • 实时调整:实时监控热门的数据,调整对应 key 的过期时间。
  • 分布式锁
  1. 线程 t1 请求访问 Redis 中的 key1 而获取不到数据时,将 key1 锁定。
  2. 若其它线程也请求访问 key1 则会进入短暂睡眠。
  3. t1 则正常执行并建立缓存,建立完成后将 key1 解锁,其它线程可读取到缓存。

3、缓存雪崩

缓存击穿的升级版

  • 击穿:一个 Key
  • 雪崩:多个 Key

缓存雪崩大量并发请求同时访问多个缓存过期的 Key

解决

  • 构建多级缓存架构:nginx 缓存 + redis 缓存 + 其他缓存(ehcache等)
  • 设置过期标志,更新缓存:设置缓存数据过期的提前量,达到后会通知另外的线程在后台更新缓存。
  • 错开缓存失效时间
  • 在原有失效时间的基础上增加一个随机值。
  • 降低每个 Key 的缓存到期时间的重复率。

穿透、击穿、雪崩的危害程度是递增的。

  • 避免缓存雪崩,前提是避免缓存穿透、缓存击穿
  • 也就是说,严重问题的解决方案是基于简单问题的。