我们前面讲过Redis可以做为DB使用,也也可以做为缓存使用。
在做为缓存使用时有可能会遇到击穿、穿透、雪崩的情况。
注:这些情况,只会在Redis做为缓存使用时才会出现,此时在我们应用的后端,必然还有一个物理DB存在
击穿
所谓击穿,就是在高并发的情况下,Redis某一个key,由于有效期到了,或者LRU策略导致被清理了,在某一个时间的瞬间,这个key不存在了,但是在高并发下此时由大量的请求同时到达,假设1w个请求,此时去查询Redis缓存,key不存在,必然导致这1w个请求都会被转发到后端的DB。此时DB很有可能会承受不住这么大的瞬间流量,连接数直接被撑爆了。
解决
一旦发生了击穿,我们该怎么解决呢?
我们分析下造成击穿的过程:当一个key没有了之后,大量的请求同时到达,都发现缓存中没有数据,我们应用会将这些查询请求都转发给了DB。
我们不难发现,这里面主要原因就是大量的请求同时到达,且请求都被转发。
相信大家很自然能想到解决方案:
当同时有大量请求达到之后,并且发现这个key的缓存不存在,此时不是同时转发这些所有请求到DB,而是对这些请求的key进行加锁,如果能够获取到这把锁才将请求转发到DB,当DB返回结果之后,将结果写入Redis,同时释放锁。
假设现在有1w个请求同时到达,此时都发现这个key对应的缓存不存在了。第一个请求尝试获取锁,获取到锁之后将请求转发到DB,其余的9999个请求只能等待锁释放。当第一个请求从DB获取到数据之后,将数据重新写入Redis,释放锁。此后剩余的请求等待一段时间后再次重新尝试获取缓存数据,此时已经可以从Redis获取到缓存数据了,不需要再将请求转发到DB。
问题
我们使用锁机制来解决高并发问题,但是锁本身可能也会有问题:
- 获取到锁的请求由于某些原因提前终止了,没有能够完成锁释放,造成了死锁。在此情况下我们应当给锁设置超时时间,到期后自动释放锁,避免死锁
- 假设我们锁超时时间设置为1ms,但是请求转发到DB后,DB执行需要20ms。也就是说当锁被自动释放时,第一个请求其实还没有处理完成,此时第二个请求可以获取到锁,有一次将请求转发到了DB。如此在20ms之内一样会将大量请求转发到DB。
在此情况下,我们可能就得在起一个伴生线程,在第一个请求索取到锁转发到DB之后,伴生线程需要关注在锁的有效期内是否已经获取到了数据,如果没有,则需要手动将锁的有效期延长。
穿透
击穿的情况,是这个key在数据库中其实是由数据的,只是由于某些原因导致缓存中不存在了这条数据,导致请求被转发到了DB
穿透的情况,是这个key在DB中也是没有数据的,缓存中就更是不存在数据了。当有大量请求同时到达之后,缓存中没有数据,请求被转发到了DB。
很明显,击穿的解决方案完全无法解决穿透的问题。
问题点什么?问题点就是由于DB中也没有对应的数据,即便请求转发到DB之后,也无法对缓存进行补偿。所以在请求转发之前,我们需要先识别下这个key在DB是否存在数据,如果不存在数据,就不需要将请求转发了。
解决
在请求转发之前,使用布隆过滤器,或者布谷鸟过滤器,先行判断数据是否在DB中存在。
雪崩
雪崩这个词还是蛮形象的,雪崩的出现,那一定是大面积的崩溃。
击穿的情况下,只是一个key对应的缓存没有了。
雪崩的情况下,是大量的key对应的缓存同时没有了,此时又有大量的请求到达。
解决
所以,击穿的解决方案,在雪崩下,可以解决单个key的问题
不过这还不够,需要解决大量key同时失效的问题。问题的关键点就在于缓存同时失效,我们需要让这些缓存的失效时间错开了,再结合击穿方案解决单点问题,雪崩问题自然就迎刃而解。
一般做法是在缓存的有效期上,加上一个随机失效时间,从而避开同时失效。
如,现有两个key,对应的失效时间都是5ms。现在对key1添加一个随机失效时间,如1ms,则其失效时间变成5+1=6ms;对key2页添加一个随机失效时间,如2ms,则key2的失效时间变成5+2=7ms,这样key1和key2便不会同时失效。