缓存雪崩、击穿、穿透

1 阅读6分钟

缓存雪崩

缓存雪崩的意思是同一时间在缓存中的大量数据过期或者Redis宕机导致缓存服务失效,这个时候大量的请求就直接打到了数据库中,导致数据库的压力骤增,严重的会导致数据库的宕机。
导致缓存雪崩的问题有两个:

  • 同一时间大量数据过期
  • Redis宕机

针对同一时间大量数据过期的问题,解决方案为:

  • 均匀地设置过期时间
    我们能够想到的第一点就是让大量数据不要是一个过期时间,而是让过期时间分散开来。如果我们设置的时候就是一个过期时间的话,我们可以对这些数据的过期时间加上一个随机数,这样就能够在一定程度上保证数据不在同一个时间过期了。
  • 互斥锁
    当业务线程发现缓存数据已经失效,从Redis中已经无法获取缓存数据的时候。这个时候有多个线程访问数据,那么这些线程要抢占一个互斥锁,只有抢占到互斥锁的线程才可以去访问数据库获得数据,并且在获取到数据之后将数据缓存到Redis中,最后释放锁。没有抢占到锁的线程,要不然等待锁释放后再次去访问缓存,要不然直接返回一个空值或者默认值。
  • 后台更新缓存
    不再为数据设置一个过期时间,而是让数据永久地保存到Redis中,并且存在一个后台线程会定期地更新缓存,但是如果系统内存紧张,可能会触发内存淘汰机制,部分缓存可能就被淘汰了。
    为了解决内存淘汰机制带来的问题,存在两种方案,第一种方案就是后台线程频繁地检测缓存是否有效,如果失效了,那么就从数据库中读取数据,然后更新到缓存;第二种方案就是业务线程在发现缓存失效之后,通过消息队列发送一条消息通知后台线程更新缓存,后台线程执行缓存更新操作。
    在业务刚上线的时候,我们需要提前将数据缓存到Redis中,而不是等待用户访问的时候再将数据从数据库加载到缓存中,这叫做缓存预热。

针对Redis故障宕机,解决方案为:

  • 服务熔断或者请求限流机制
    当Redis故障宕机之后,我们可以采用服务熔断的机制,业务线程无法再访问缓存服务,直接返回错误,不让它访问数据库,从而降低数据库的访问压力,直到Redis恢复正常后,才允许业务线程访问缓存服务。
    我们还可以采用请求限流机制,当Redis故障宕机的时候,我们只允许少量的请求访问数据库,其他的请求就直接被拒绝访问,等到Redis恢复正常之后,再允许访问,这样可以保证业务的正常运行。
  • 构建Redis集群
    服务熔断或者请求限流机制都是发送了Redis宕机之后的应对方案,真正想要完全解决这个问题还是需要Redis的主从节点,当主节点宕机之后,将从节点通过哨兵机制升级为主节点,从而继续提供缓存服务。

缓存击穿

缓存击穿指的是缓存中的热点数据过期,从而导致本应该访问热点数据的大量请求达到了数据库上,从而导致数据库的压力骤增,可能会被高并发的请求冲垮。
我们看缓存雪崩和缓存击穿的定义可以发现,两者是非常相似的,区别在于缓存击穿是热点数据,缓存雪崩是大量数据,那么解决方案其实也是类似的。

  • 互斥锁的方案。在热点数据过期之后保证同一时间只有一个线程拿到互斥锁,从数据库中获取数据之后再释放锁,没有拿到锁的线程要么等待锁释放之后再重新读取缓存,要么返回空值或者默认值。
  • 不给热点数据设置过期时间,而是由后台线程每隔一段时间定期更新缓存中的值,或者在热点数据将要过期的时候,提前通知后台线程更新缓存以及重新设置过期时间。

缓存穿透

缓存穿透指的是当用户访问的数据既不在缓存中,也不再数据库中,导致我们在访问数据库拿到数据之后也没有办法通过将数据库中的数据缓存到Redis中,从而服务后续的请求,避免短时间内再次发生相同的情况。当有大量这样的请求到来的时候,请求被打到了数据库中,数据库的压力骤增。
缓存穿透发生的原因:

  • 由于误操作导致缓存和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据。
  • 用户恶意攻击,故意使用不存在的数据来访问业务。

针对缓存穿透,解决方案有:

  • 限制非法的请求:针对用户发送请求过程中的请求信息,例如请求参数,我们需要根据业务的情况加以判断,假设我们的请求参数是age,那么如果出现了不合法的数字,例如-1,我们拒绝这个请求访问缓存和数据库,而是直接返回异常。
  • 缓存控制或者默认值:如果用户的请求打到了数据库中,在数据库中也没有查询到对应的数据,我们可以为这个请求在缓存中构建一个空值或者默认值,这样在后续用户再次执行该请求的时候,可以直接在缓存中查询到对应的值,从而不会再查询数据库。
  • 使用布隆过滤器判断数据是否存在,这样就不用通过访问数据库来判断数据是否存在了。当我们将数据写入数据库的时候,我们也需要将对应的数据加载到布隆过滤器中。加载方式为该数据首先会经过N个哈希函数计算出N个值,每个值使用布隆过滤器的长度进行取模,然后在对应的位置赋值为1。当业务线程访问缓存的时候,发现数据过期了,那么去布隆过滤器中进行查询,判断数据是否存在于数据库中,如果查询结果都为1,那么就查询数据库获取值,如果存在位置不为0,那么就不让这个请求访问数据库。但是布隆过滤器存在假阳性的问题,布隆过滤器判断存在的值不一定真存在,但是布隆过滤器判断不存在的值一定不存在。