本章我们说一下,缓存中常见的异常问题,而这一部分我们将其分为了缓存中常见的数据不一致问题,和缓存的三大常见异常:1.缓存击穿 2.缓存雪崩 3.缓存穿透
对于数据不一致,则可以简单的理解为缓存和数据库之间的不一致性
那么,对于实际场景中涉及的数据不一致问题,拿一个应用向MySQL中写入和删除数据为例,解释一下,数据的增删改操作如何具体进行的
Tomcat上的运行应用,无论增删改,都会直接在数据库中修改,然后再删除数据的缓存
我们分不同的场景来看
1.如果是新增数据,数据会直接写到数据库中,不需要对缓存做任何操作,此时缓存中本身就没有新增数据,数据中是最新值,这时候就符合我们说的第二种情况,缓存和数据库的数据一致
2.删改数据,因为需要更新数据库的时候,删除缓存中的数据,这样就有着数据不一致的问题
如果是先删除缓存,在更新数据库,那么如果有额外的线程这时候访问数据,就会获得旧数据,从而在缓存中缓存了旧值,导致应用一直访问缓存获得旧值
如果是先更新数据,再删除缓存,那么在更新数据库的时候,如果有额外线程进行访问,读到的还是旧值
从上可以看出,无论哪个操作谁先谁后,都有读到旧值的可能性
一种比较传统的解决方案是重试机制
将数据库中更新后的数据暂存到消息队列,然后让消息队列的消费者去更新Redis,保证数据的一致性
但是在很多情况下,还是可能出现读到旧值的可能性
故,对于缓存中的数据不一致问题,除了使用分布式事物,不然很难进行解决
那么接下来我们说一下Redis中常见的三种缓存异常
首先是缓存雪崩问题
缓存雪崩指的是大量应用请求无法利用Redis作缓存,导致直接访问了后端数据库,导致数据库压力激增
很有可能伴随着Redis的宕机出现的,或者是有大量的数据同时过期,导致大量请求无法得到处理
对于此情况的预防,可以作如下的方案
第一,对于明确需要同时失效的数据,可以在使用EXPIRE命令设置过期时间的时候,给这些过期时间增加一个较小的随机数,保证数据在一个时间段内过期的同时,避免雪崩问题
第二,对于Redis,构建高可靠集群,避免由于缓存实例宕机而导致的缓存雪崩问题
第三,对服务建立限流机制,确定每个服务能够接收的服务上线,避免过多的压力激增导致的缓存崩溃,连带的服务应用崩溃
第四,建立服务熔断机制,为了避免在缓存雪崩时,出现的大量请求打崩整个系统的可能,需要可以暂停调用缓存系统相关接口,在出现雪崩的时候,我们可以直接将缓存调用设置为返回一个缺省值
然后是缓存击穿问题
缓存击穿则是对于一个热点数据,如果突然数据过期了,导致原本访问此热点的大量请求,一下子都发送到了后端数据库,增大数据库的访问压力
对于此问题的解决方案,也比较简单,对于热点数据,就不设置过期时间了,方便读取缓存数据
最后是缓存穿透问题
就是当客户一直请求一些既不在Redis中,也不再数据库中的数据,导致每次访问的时候都去访问数据库查询一次,给缓存和数据库带来了巨大的压力
这种情况,一般存在于恶意攻击和数据丢失两种情况中
为了避免这种情况,我们可以考虑先缓存一个空值或者缺省值,供应用层读取后返回
然后再有数据的时候,将缓存删除,然后缓存实际的数据
以及考虑使用布隆过滤器来判断数据是否存在
我们设置一个N位的bit数组,其初始值都是0
然后我们使用多个哈希函数,对于数据的请求的哈希值进行计算,得到多个哈希值
然后我们分别查看这些哈希值对应的bit数组上的位是否为1,如果都为1,就是存在,如果有为0的,那么就不存在
那么我们就利用布隆过期率的快速检测特性,在数据库写入的时候,进行标记,方便快速判断数据是否存在,不存在就不去数据库查询了,避免缓存穿透
那么总结一下,我们说了缓存异常中的数据不一致,以及缓存雪崩 击穿 穿透几个常见的异常问题,
数据不一致,只能通过分布式锁来解决,而缓存雪崩 击穿 穿透,则有着不同的解决方案
最后提出一个小问题,对于缓存穿透,可以考虑使用熔断 降级,限流这几种方法吗
这几种方案本身就是饮鸩止渴,对于缓存穿透中可能出现的恶意攻击,是可以利用限流和熔断,降级避免应用崩溃,但是也对正常的用户体验下降了,所以还是考虑从客户请求校验这一层次考虑更好