缓存穿透、缓存击穿、缓存雪崩
1、什么是缓存穿透、缓存击穿、缓存雪崩?
1.1、什么是缓存穿透?
缓存穿透是指查询一个缓存中和数据库中都不存在的数据,导致每次查询这条数据都会穿透缓存,直接查库,最后返回空,当用户使用这条不存在的数据疯狂发起查询请求的时候,对数据库造成的压力就非常大,甚至可能直接挂掉。这种情况的流程变成了下图这个了
1.2、什么是缓存击穿?
缓存击穿是指当缓存中某个热点数据过期了,再该热点数据重新载入缓存之前,有大量查询请求穿过缓存,直接查询数据库。这种情况会导致数据库压力瞬间骤增,造成大量请求阻塞,甚至直接挂掉
1.3、什么是缓存雪崩?
缓存雪崩是指缓存中再同一时刻有大量key过期,或者redis直接宕机,导致大量的查询请求全部到达数据库,造成数据库压力骤增,甚至直接挂掉
2、如果解决缓存穿透、缓存击穿、缓存雪崩?
2.1、缓存穿透的解决方案
解决缓存穿透的方案一般有两种,第一种是缓存空对象,并设置过期时间,第二种是使用布隆过滤器
第一种比较好理解,就是当数据库中查不到数据的时候,我缓存一个空对象,然后给这个空对象的缓存设置一个过期时间,这样下次查询该数据的时候,就可以直接从缓存中拿到,从而减小了数据库的压力,当这种解决方案有两个缺点:
①需要缓存层提供更多的内存空间来缓存这些空对象,当空对象很多时,会浪费更多的内存
②会导致缓存层和存储层数据不一致,及时在缓存空对象设置很短的过期时间,也会导致这一段时间内的数据不一致的问你他
第二种方案是使用布隆过滤器,这是比较推荐的,所谓布隆过滤器,就是一种数据结构,它是由一个长度为m bit的位数组和n个hash函数组成的数据结构,位数组中每个元素的初始值都是0,在初始化布隆过滤器是,会先将所有的key进行n次hash运算,这样就开业得到n可位置,然后将n个位置上的元素改为1,这样就相当于把所有的key保存到了布隆过滤器中
举个例子,比如我们一共有3个key,我们对这3个key分别进行3次hash运算,key1进过三次hash运算后的结果分别是2/6/10,那么就把布隆过滤器中下表为2/6/10的元素值更新为1,然后再分别对key2和key3做同样的操作,结果如图所示
这样,当客户端查询时,也对应查询的key做三次hash运算得到3个位置,然后看布隆过滤器中对应位置的元素是否是1,如果所有的对应位置的元素都为1,就证明key在库中存在,则继续向下查询,如果三个位置中有任意一个位置的值不为1,那么证明key在库中不存在,直接返回空即可。(为什么要hash三次,避免有些值会有相同的hash值,会占据布隆过滤器的下标位,进行多次hash后,大大避免了这个问题)
所以布隆过滤器就相当于一个位于客户端与缓存层中间的一个拦截器,负责判断key是否存在集合中。
布隆过滤器的好处就是解决了缓存控制的不足,但布隆过滤器也存在缺陷,首先它有误判的可能,比如key4进过三次hash运算得到的位置分别是2/4/6,但这三个位置的值都是1,所以,布隆过滤器就认为key4在库中存在,进而继续向下查询了,所以,布隆过滤器判断存在的key实际上可能会是不存在的,但不布隆过滤器判断不存在的key是一定不存在的,她的第二个缺点是删除元素比较难,如果现在要删除key2元素,那么需要将2/7/11的三个位置的元素改为0,那这样会影响key1和key3的判断
不过都需要根据业务场景使用对应的方案
2.2、缓存击穿的解决方案
解决缓存击穿的方法也有两种,第一种就是设置key永不过期,第二种就是使用分布式锁,保证同一时刻只能有一个查询请求重新加载热点数据到缓存中,这样其他的线程只需等待该线程运行完毕,即可重新从redis获取数据
第一种比较简单,在设置热点key的时候,不给key设置过期时间,不过还有另一个方式也可以达到key不过期的目的,就是正常给key设置过期时间,不过在后台同时启动一个后台线程定时去更新这个缓存
第二种方式使用加锁的方式,所的对象就是key,这样,当大量查询同一个key的请求并发进来时,只能有一个请求获取到锁,然后获取到锁的线程查询数据库,然后将结果放到缓存中,然后释放锁,此时,其他处于锁等待的请求即可继续执行,由于此时缓存层已经有了数据,所以直接从缓存中获取到数据并返回,并不会查询数据库
2.3、缓存雪崩的解决方案
针对第一种大量key同时过期的情况,解决起来比较简单,只需要将每个key的过期时间打散即可,使他们失效点尽可能均匀分布
针对第二种redis发生故障,部署redis高可用方案