项目中常使用Redis作为系统的缓存,在实际高并发场景下,稍有不慎,就会造成缓存穿透、缓存击穿和缓存雪崩的问题。那什么是缓存穿透?什么是缓存击穿,又什么是缓存雪崩呢?它们是如何造成的?又该如何解决呢?本文就对这三种问题进行剖析。
缓存穿透
key对应的数据在数据库中并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据库,从而可能压垮数据库。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
缓存击穿
热点key对应的数据存在,但已过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从数据库加载数据并回设到缓存,那么大并发的请求可能会瞬间把数据库压垮。
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
缓存穿透解决方案
接口层增加参数校验,比如用户鉴权校验,参数做校验,不合法的参数直接代码Return,避免非法请求。
把空对象缓存起来。当第一次从数据库中查询出来的结果为空时,我们就将这个空对象加载到缓存,并设置合理的过期时间(比如设置5分钟过期),这样,就能够在一定程度上保障后端数据库的安全。
采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一定不存在的key请求会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
缓存击穿解决方案
key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。
对于热点的数据,可以在缓存中设置这些数据永不过期。
可在访问数据的时候,在缓存中更新这些数据的过期时间,达到自动续期效果。
使用分布式锁,保证对于每个Key同时只有一个线程去查询后端的服务,某个线程在查询后端服务的同时,其他线程没有获得分布式锁的权限,需要进行等待。不过在高并发场景下,这种解决方案对于分布式锁的访问压力比较大。
缓存雪崩解决方案
与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key。
一个简单方案将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低
使用限流降级的方式防止缓存雪崩。例如,在缓存失效后,通过加锁或者使用队列来控制读数据库写缓存的线程数量。具体点就是设置某些Key只允许一个线程查询数据和写缓存,其他线程等待。则能够有效的缓解大并发流量对数据库打来的巨大冲击。
通过数据预热的方式将可能大量访问的数据加载到缓存,在即将发生大并发访问的时候,提前手动触发加载不同的数据到缓存中。