Redis缓存

由于Redis追求速度将数据存在缓存层中,就会有缓存异常的三个问题,分别是缓存雪崩、缓存击穿、缓存穿透

此表格为下文的总结 :

缓存异常名称 引发原因 解决办法
缓存雪崩 针对大量数据(key)在同一时间失效 不同的失效时间/互斥锁/后台更新缓存
Redis宕机 Redis集群/限流
缓存击穿 热点数据失效 设置热点数据不过期或长过期时间/缓存预热/互斥锁
缓存穿透 大量Key不存在缓存与数据库中 参数校验/缓存无效Key/布隆工作器

缓存雪崩

缓存雪崩指的是缓存在同一时间大面积的失效,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力。

缓存在同一时间内大面积失效的情景 : 大量数据(key)在同一时间失效 或 Redis宕机。

解决办法

针对大量数据(key)在同一时间失效:

  • 设置不同的失效时间:可以给失效时间加上一个随机数。
  • 互斥锁;当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。(实现互斥锁的时候,最好设置超时时间,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。)
  • 后台更新缓存;指缓存的更新不再交给业务线程,而是用一个后台线程定时更新缓存,造成缓存不会过期的“假象”。但是当内存紧张淘汰一些缓存时,业务线程会无法获取缓存。解决办法是让业务线程发现缓存数据失效后,就通知后台线程更新缓存,后台线程收到消息后,再更新缓存。

针对Redis宕机:

  • 采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。
  • 限流,避免同时处理大量的请求。

缓存击穿

数据库中一些数据会被频繁的访问,比如被秒杀的商品,这类数据就叫做热点数据。

如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。可以认为缓存击穿是缓存雪崩的一个子集。

解决办法

  • 设置热点数据永不过期或者过期时间比较长。
  • 针对热点数据提前预热(即在业务刚上线的时候,提前把数据缓起来而不是等待用户访问才来触发缓存构建),将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。
  • 请求数据库写数据到缓存之前,先获取互斥锁,保证只有一个请求会落到数据库上,减少数据库的压力。

缓存穿透

缓存穿透说简单点就是大量请求的 key 是不合理的,根本不存在于缓存中,也不存在于数据库中 。这就导致这些请求直接到了数据库上,根本没有经过缓存这一层,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。

缓存穿透的发生一般有这两种情况:

  • 业务误操作,缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据;
  • 黑客恶意攻击,故意大量访问某些读取不存在数据的业务;

解决办法

  • 参数校验。最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。

  • 缓存无效 key。如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,这种方式可以解决请求的 key 变化不频繁的情况,但如果有黑客构建大量不同的key,那么此方法就不能解决。

  • 布隆过滤器可以非常方便地判断一个给定数据是否存在于海量数据中。要预先把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。

    布隆工作器的加入元素时:

    1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
    2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。

    布隆工作器判断元素是否存在时:

    1. 对给定元素再次进行相同的哈希计算;
    2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。

    由于不同的字符串可能哈希出来的位置相同。因此布隆工作器如果判断在,那还有小概率不在。如果布隆工作器判断不在,那么一定不在。(可以适当增加位数组大小或者调整哈希函数来降低两个字符串哈希结果相同的概率)