缓存穿透、雪崩、击穿解决方案

420 阅读5分钟

一、缓存穿透

1、什么是缓存穿透

​ 正常情况下,使用缓存的话,大部分数据都会命中缓存(缓存不存在或者过期的话,也只有一次会查询数据库),可以极大的减轻数据库的压力。

​ 如果发生一些攻击行为,攻击方伪造了一些数据(缓存不存在,数据库也不存在),那么每次查询都会去查数据库,对数据库造成压力。称这种行为为缓存穿透

2、带来的问题

​ 大量的请求直接穿过缓存打到数据库上,很容易造成数据库宕机,服务不可用。

3、解决方案

3.1、缓存空值

​ 之所以会发生穿透,就是因为缓存中没有存储这些空数据的key。从而导致每次查询都到数据库去了。那么我们就可以为这些key对应的值设置为null 丢到缓存里面去。后面再出现查询这个key 的请求的时候,直接返回null 。 这样就只会查一次数据库,但是别忘了设置过期时间。

​ 这里可能有同学会有疑问:即使是缓存空值也是需要查一次数据库的,那么如果攻击方的key非常随机,那么不是依旧有大量的请求打到数据库上吗?

​ 这里个人的一个想法是:

1、每次的请求都非常随机,并且key都不在数据库,几率比较小,出现这种情况就是你的代码和数据泄露了。

2、即使攻击方的确很强悍,就是做到了随机。那么对于这种情况就需要服务端实现限流了,一般是不允许用户这么频繁的请求的。

3、如果攻击方有ip池,每次都是不同的身份来发起请求攻击。嗯!!!,关服务器吧,让他没法攻击。开个玩笑,一般如果到这种地步的,就需要专业的安全同学介入了。

3.2、BloomFilter(布隆过滤器)

来源:布隆过滤器

如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为O(n),O(logn),O(1)

布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。

布隆过滤器相比散列表而言,占据的空间更小(因为一般是位数组存储),但是通过上面的描述我们可以知道布隆过滤器告诉你不存在就肯定不存在,告诉你存在那么可能在也可能不在

一般情况下不能从布隆过滤器中删除元素。

我们可以提前将数据初始化到布隆过滤器里,请求过来时,如果布隆过滤器判断不存在那么就直接返回,如果存在那就查缓存->查数据库。

不过这样的有以下缺点:

  • 数据量很大的情况下,缓存预热需要的时间较长。

  • BloomFilter无法删除,一段时间后,可能效果变差。

  • 数据库数据实时再变,可能导致需要手动订正BloomFilter中的数据。

二、缓存击穿

1、什么是缓存击穿

​ 对于一些设置了过期时间的key,如果这些key是“热点”的数据。比如热门微博,这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,雪崩则是很多key。

​ 缓存在某个时间点过期了,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求就会打到数据库上,这个时候请求量很大的话可能会瞬间把后端DB压垮。

2、解决方案

加锁:通过对请求进行加锁,同一时刻只允许一个请求去访问数据库,其他的线程就在自旋的访问缓存,一旦缓存就有数据了就直接返回。

三、缓存雪崩

1、什么是缓存雪崩

​ 大量的缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

同时失效的原因可能有如下几个:

  • 大量key设置了相同的失效时间

  • 缓存服务器宕机了

2、解决方案

​ 2.1、针对大量缓存同时失效的情况,可以在为key设置过期时间时加上一个随机的时间值

​ 2.2、针对缓存服务器宕机,可以考虑使用主备或者集群(比如redis主从+哨兵、redis Cluster)

​ 2.3、限流:一般对于高可用,缓存、限流、熔断是必不可少的,限流可以在缓存失效的基础上保护数据库不被打挂。