缓存穿透、缓存击穿、缓存雪崩解决方案?
话不多说先上生动的例子:
我们有个pv上亿的活动页面,页面上好多信息都缓存在redis中,碰巧我们设置的key的过期时间都是12点。在key过期的一瞬间,我有个活动开奖,大量用户涌入该页面,假设当时每秒5000个请求,原本缓存可以扛住每秒4000个请求,但是缓存失效。此时5000个请求全部落到数据库,数据库一下就报警了,此时DBA没反应过来就直接挂了。这个时候如果没有预案,直接重启数据库,新的请求会不断涌进来,数据库立马又被打挂,这就是一种缓存雪崩的表现。
如果雪崩发生在核心用户服务模块,同一时间大面积失效,redis形同于无,这个数量级的请求直接打到数据库,此时如果没有做熔断降级,所有依赖用户服务的接口几乎都会报错,只要用户请求没有减少,无论怎么重启,都是直接被打挂,等能重启的时候,用户估计早就卸载app了,甚至感叹一句,这什么垃圾产品..
那我们应该如何应对缓存雪崩的问题?
1.批量写入redis缓存数据的时候,把每个key的失效时间都加一个随机值,这样可以保证数据不会在同一时间大面积失效。
setRedis(Key,value,time+Math.random()*1000);
2.redis集群,将热点数据分布在不同的redis库中
3.设置热点数据永不过期,有更新操作就更新缓存
缓存穿透:
缓存穿透是指缓存和数据库中都没有的数据。例如一个攻击者查询一个不可能在数据库中存在的id值,高频的请求会导致数据库压力过大,严重会打挂数据库。
穿透如果一直用小于0的参数去请求,每次在redis中都查不到,会直接打到数据库,数据库也查不到,此时并发足够高就容易打崩db。
穿透的解决方案:
1.布隆过滤器
利用高效的数据结构和算法快速判断出key是否在数据库中,不存在就return。
2.接口层对参数进行校验,不合法的参数直接代码过滤return,比如id<=0则直接return
理论上对服务端来说,前端的所有参数都是不被信任的,我们要考虑到参数的所有可能情况,并作出合理校验。
在缓存和数据库中都没有查到的数据,可以缓存一个flag,但是缓存时间不应设置过长,以免影响正常使用。这样可以防止攻击者频繁使用同一个id暴力攻击。
从防止攻击的角度来说,也可以在网关层对ip设置访问阈值,超出阈值直接拉黑。
缓存击穿:
击穿可以认为是雪崩的一个特例。击穿不是大面积的缓存失效,而是热key失效,导致持续的大并发打到数据库,就像在redis中凿开了一个点。
解决方案:
设置热点数据不过期,或者加互斥锁
互斥锁:请求进来发现redis里没有数据,则直接加锁到数据库请求,然后塞入redis,解锁。加锁期间所有请求直接返回空。相当于一种降级方案。