Redis缓存穿透、击穿、雪崩介绍以及解决方案
一、缓存穿透
1.1 什么是缓存穿透?
指的是,外部进来的请求,查询一个不存在的数据。Redis中没有,数据库中也没有,这时候如果外部恶意大量请求,所有请求会直接查询数据库,导致数据库崩溃
1.2 解决方案
1.2.1 方案一:缓存空数据
缓存空数据到redis中,比如恶意请求查询编号为1的用户,我查询数据库后发现不存在,就直接在redis中添加键值对:{key: 1, value: null},这样外部再次大量请求也会直接请求在redis上,不会导致数据库异常
优点:简单
缺点:1、如果有大量的随机查询,比如查询编号为1,2,3,4,5......到一万亿的用户,我都保存一个键为编号,值为null的数据,那么对redis的内存压力也是很大的,保存了太多无用数据
2、如果编号为2的用户本来不存在,我现在在redis中设置了一个null的值;如果这个时候真的添加了这个编号为2的用户,那么外部查询也查询不到了:数据不一致
1.2.2 方案二:布隆过滤器
布隆过滤器是指,将键经过多次hash之后以0和1的数组格式,保存在一个位图(bitmap)中,一个请求进来,先把请求中查询的键去位图里查询,如果存在,再去查询Redis,如果不存在则连redis都不会去查,直接返回;
优点:位图bitmap占用内存较小,不像缓存空数据那样缓存大量的不需要的key
缺点:1、实现起来复杂,热点数据从数据库查询出来后,也要写入过滤器中
2、存在误判,如果一个不存在的数据,多次hash之后,在位图中的位置可能显示存在此条数据;导致查询数据库,但是总体上影响不大,误判率可自己在写代码时进行控制
二、缓存击穿
2.1 什么是缓存击穿?
当给某一个热点数据,设置了过期时间,当key过期时,恰好这段时间有大量的并发请求进来,那么可能会把数据库压垮
疑问:key过期了,Redis中查询不到,会去数据库查询,查询完成后不是又会写入redis中吗?为什么会导致大量请求到数据库呢?
解答:查询数据库并写入redis也是需要时间的,有些写入redis的结果集,是需要多表查询的结果,比如需要50ms的,甚至几秒钟才能从数据库查询出来的,那么这段时间就太长了
2.2 解决方案
2.2.1 互斥锁
当key过期后,后进来获取该key的第一个线程,在redis中查询不到,未命中;获取一个互斥锁,然后去查询数据库,获取结果后写入缓存,并释放锁;这段时间内,其他所有线程未命中redis中的数据,尝试获取锁失败(因为锁被线程1拿到了),导致获取锁的等待
优点:强一致性,数据一定是最新最正确的
缺点:要等到线程1查询到,写入redis中并释放锁之后,其他线程才能查询这条键值对的数据
2.2.2 逻辑过期
热点key不设值过期时间,而是加一个逻辑过期时间的字段,用来描述这整条数据是否逻辑过期;
当某个线程1拿到这条数据后,发现逻辑过期时间,已经过期,那么会新开一个线程2去进行缓存重建;而线程1不等待缓存重建的结果,直接将过期的数据返回回去
如果此时有更多的线程3来获取key发现过期,尝试获取锁失败后,会直接返回过期的数据,并不会开启新线程来进行缓存重建,因为只需要一个线程2进行缓存重建就可以了
当线程2缓存重建完成后,再之后的线程4就能获取到最新的数据了
优点:高可用(不管数据一致不一致,先返回了再说),性能优
缺点:不能保证数据绝对一致
三、缓存雪崩
3.1 什么是缓存雪崩?
是指同一时间大量的缓存key同时失效,或者Redis服务宕机,导致大量请求打到数据库,导致数据库压力巨大
3.2 解决方案
3.2.1 不同的过期时间
给不同的key设置不同的过期时间,可随机设置过期时间
3.2.2 Redis集群部署
利用Redis集群提高服务的高可用性,例如哨兵模式、集群模式等
3.2.3 降级限流策略
在redis之前添加降级限流策略,比如nginx、spring cloud gateway等,可以同时作为缓存穿透、击穿、雪崩的保底策略,不止是防范缓存雪崩
3.2.4 添加多级缓存
在redis之前,使用其他caffeine缓存等来做一级缓存,Redis作为二级缓存
附上《缓存三兄弟》及其解决方案打油诗一首:
穿透无中生有key,布隆过滤null隔离
缓存击穿过期key,锁与非期解难题
雪崩大量过期key,过期时间要随机
--黑马程序员
注:其中的“锁与非期解难题”中的非期指的是逻辑过期