Redis中的缓存穿透、击穿、雪崩问题
编辑缓存穿透、击穿、雪崩,是缓存中非常常见的三个问题(面试也很爱问)。
在介绍之前,我们先来看下,当一个系统中引入Redis作为缓存,数据是怎么查询的。
通常就长图中这样,我们来看:
首先 我们的服务会先向缓存(Redis)查询,如果缓存命中(也就是缓存里面有),就直接在缓存中返回
要是缓存没命中,就去向数据库查询
数据库将数据返回,并将数据缓存到Redis中
OK 很完美 这就像硬盘的缓存一样,加速了热点数据的查询效率
缓存穿透
缓存穿透: 指的是当查询一个缓存中不存在的数据时,此时查询的操作就会打到数据库中,由于数据查询的结果为空,且此时数据库查询到的数据不会被缓存
这就导致了什么问题,欸,这个缓存有跟没有一样,每次的请求都会查询数据库。
一个两个倒还好,要是请求达到一定的量,就有可能导致数据库服务宕机
那什么情况会出现缓存穿透,举个例子:
当一个电商系统,我查询一个不存在的商品的ID,这时候,数据库中就不存在这个商品,也就返回空值
为什么会出现查询一个不存在的商品的这种不正常的情况呢:
可能是由于开发人员的疏忽,导致发出错误的请求
或者有可能是因为数据更新的不及时,导致已经被删除的ID又被查询
最大的可能就是遭受恶意攻击
当有心之人获取到你的接口地址,得知了接口的参数,就可以通过某些脚本,向接口发送了大量的带着错误的参数的请求,哦吼
解决方法
有没有办法解决,有!
缓存空对象:
这个是网上讲的最多的方法,也就是当数据库返回空数据时,照样对空数据进行缓存
但有个问题,要是有心之人传递的参数是随机值,那缓存中还是不会命中,且大量的缓存也会浪费内存
即使不是恶意攻击,要是在第一次查询缓存空对象后,数据库中插入了对应的数据,那就会导致数据不一致
所以,我不建议对缓存中的空对象设置太长的过期时间
布隆过滤器:
什么是布隆过滤器:布隆过滤器通常采用一种空间效率极高数据结构存储,用于测试一个元素是否在集合中
对于缓存穿透问题,我们可以在缓存预热阶段,将ID存储在布隆过滤器中
在对Redis进行查询之前,先查询布隆过滤器,若布隆过滤器不存在这个ID,就直接返回Null,不往下走
这不仅仅环节了数据库的压力,也减低了Redis的压力,真好
布隆过滤器的事项方法有很多,例如guava库中就有相应的实现,本文不深入讨论
我们来讲讲缺点:首先,布隆过滤器的实现是会比缓存空对象要复杂一点的
而且,布隆过滤器本身是存在误算的(当然也容许误算的存在),我这拿黑马课程中的一张图举例
id1和id2通过多个哈希函数进行计算,得到多个位置索引,但id3(数据库中不存在)多次计算后的结果与id1、id2对上了,也就是说,在布隆过滤器中,会判断id3存在
对于这种问题,通常可以通过加大数组的大小来缓解(大了就不会那么巧对上了),但随之而来的也就内存消耗的增加(现实中的数组不会像示意图这么小),在一些库中,通常误算率可以设置
当然方法不止这些,还有些通用的方法我放到最后
缓存击穿
缓存击穿: 缓存击穿是指一个热点Key在缓存中过期的一瞬间,在缓存重建的这个间隙,同时有大量的请求访问这个Key,由于缓存过期,这些请求都会打到数据库上,从而可能导致数据库压力剧增,甚至崩溃
击穿也就是像手中的盾突然间碎了,缓存起不到对数据库"保护"的作用
解决方法
互斥锁:
解决方法其实也很明显,大量请求,我只让一个线程去访问数据库,其他的等缓存有了再拿缓存不就好了
欸,互斥锁就能保证只有一个线程去查询数据库
来了个线程一,看到Redis中没有缓存,就添加一个互斥锁,然后向数据库中查询数据
此时来了个线程二,Redis中还没缓存,它也去向数据库查询,但发现互斥锁没有解锁,那就等着,一段时间看看Redis中有缓存了没有
等线程一重建了缓存,线程二重试的时候就拿到了,也就不会去访问数据库了
缺点也很明显,当缓存过期时,其他线程要等待缓存重建,比较适合对数据有强一致性的场景
逻辑过期:
逻辑过期,那就不能和之前一样再Redis中设置过期时间了
我们可以在Value中多存储一个属性,表示过期时间
这时候线程去访问Redis,就肯定拿得到数据(Key对的话),拿到数据后,判断过期时间是否小于当前系统时间
要是小于,就去向数据库发起请求,拿到新的数据,并更新缓存,新数据的过期时间肯定也是一个全新的啦
欸,发现个问题,此时要是多个线程同时拿到的数据都是过期的,拿岂不是又要大量的请求数据库
所以,逻辑过期需要配合互斥锁一起使用,好处是其他线程可以不等待锁释放,可以返回拿到的过期的数据
缺点也很明显,线程返回的数据可能不是实时的数据,这种策略通常用在高可用的场景中,性能好
部分热点数据永不过期:
这个有点狠,对于部分不会变动的热点数据,我们设置他永不过期,通常很少用这种
缓存雪崩
缓存雪崩:是指缓存中大量数据同时过期,或者缓存服务宕机,导致大量请求直接落到数据库上,使数据库压力过大,甚至崩溃。
都说雪崩时,没有一篇雪花是无辜的,所以对于这些不同Key的缓存丢失,我们都要照顾到
解决方法
设置不同的过期时间:
针对同时过期,我们可以想方法然他们岔开
这么做,很简单,在原有TTL上加上一个随机数,具体多少以业务位准
嘿,就这么简单,既不对原有过期时间的设计产生太大影响,也岔开了这些数据的过期时间,完美
Redis集群部署:
一台挂了,可能性比较大,集群中多太都挂了,这概率就小不少,这我就不多说了,以后要是想起来的话单独再写篇文章
还有一些通用的方法
其实仔细看刚刚的三个问题,都有个共同点,大量的请求
对于这种高并发场景,也不一定要从Redis上下手
我们可以从接口的角度思考,用Sentinel给接口限流、熔断,用hystrix做降级,都是解决思路,不妨融合些其他技术,或许有新的解决方案。
参考资料:
https://www.bilibili.com/video/BV1yT411H7YK https://blog.csdn.net/qq_41125219/article/details/119982158 https://www.cnblogs.com/liyulong1982/p/6013002.html
- 0
-
赞助
微信支付宝 -
分享