Redis 缓存击穿,缓存穿透,缓存雪崩原因+解决方案

发布时间 2023-09-11 20:18:41作者: 小超和你

缓存击穿,缓存穿透,缓存雪崩的原因

  • 缓存击穿:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
  • 缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
  • 缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。

缓存击穿的解决方案

使用互斥锁(mutex key)业内常用

若有大量的请求直接打到数据库上,会导致数据库崩溃,那么就先用redis互斥锁,使得所有的请求只有一个请求能拿到资源,等查到数据后,再将数据写回缓存。

其余的请求申请不到锁的时候,意味着已经有请求拿到资源了,所以只需要sleep一段时间(等数据写入缓存),再次去读取缓存即可。

让redis的key永不过期(可行但不好)

  • 设置key永不过期,在修改数据库的时候,同时更新缓存。
  • 在key将要过期的时候,去更新这个key,延长他的过期时间。
  • 分级缓存,一级缓存失效,还有二级缓存垫背。

为什么不好?因为数据的量巨大,我们的 redis 缓存,是基于内存的,一个单点,一般也不会分配过大的内存,来保证它足够灵活。

如果永不过期,那么redis就会越来越臃肿,没有初衷了。

 

使用redis锁的解决方案思路图:

针对互斥锁的思路完善这个解决方案

问题1

如果用户请求完锁之后还没来得及释放就挂了,那么锁就会一直被占用,后面的用户就不能正常工作了。

解决方案:给这个锁设置过期时间,保证无论如何一定会被释放。

问题2

如果用户请求完锁之后,锁到期了依旧没有完成业务,那么其余的用户就会抢占这个锁,互相卡来卡去超时。

解决方案:开一个线程去监控锁的过期时间,如果锁临近过期了还没完成事务,就会延长锁的过期时间。

应用:Redisson自带的看门狗函数。

问题3

如果碰见redis集群的场景,且申请锁的redis挂了,那么就会影响redis的主从一致性。

解决方案:加锁的 redis 结点,只要满足,N/2+1,也就是过半,即代表加锁成功。(Redlock)

在整个加锁过程中,整个加锁的过程,不能超过锁的有效时间,否则,就应算作加锁失败,要立刻清除所有单独结点上的锁。

 

缓存穿透的解决方案

布隆过滤器

将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

用bitmap,采用多次哈希的方式,每一次哈希都往一个槽位写上 1; 当来查询一个不存在的 key 的时候,就可以进行同样的多次哈希,第一次可能碰巧撞对,得到 1,但是后面还有两次,这样,就不一定有那么好的运气,还能够撞对。 因此,这样,可以降低缓存穿透的概率。

布隆过滤器的示意图

 

无效值也加入redis

比较简单的,就是选择,当用户查询不存在的数据时,将这个 key,存入 redis,然后用一个特殊的 value 来表示,这是一个不存在的数据。
缺点:但是,如果有大量的请求,都请求各不相同的不存在的数据,那么,redis 的缓存,就会用来存储大量没用的数据,就会造成空间的浪费。

 

缓存雪崩的解决方案

有一个简单方案就时将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

或者Redis查DB数据的时候将请求加一个随机延迟时间,让请求不要集中。

 

一般随机失效时间就已经能完成任务了,但是如果有强更新强同步的需求(12点清空数据等)。

第二个随机延迟时间的方法能完成需求,因为随机延迟时间请求,一方面能降低请求的压力,第二方面第二波请求可以从redis读取数据,进一步降低压力。