Redis基础(八)—— 缓存穿透、击穿、雪崩

1,704 阅读6分钟

这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战

Redis 缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么不能使用缓存。

我们只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性。合适的策略包括 合适的缓存更新策略,更新数据库后要及时更新缓存、缓存失败时增加重试机制,例如MQ模式的消息队列。

典型问题:缓存穿透、缓存雪崩、缓存击穿

缓存穿透

用户大量并发请求的数据(key)对应的数据在 Redis 和 持久层数据库 中都不存在,导致尽管数据不存在但还是每次都会对持久层数据库的查询。

出于容错的考虑,如果从持久层查不到数据则不会写入缓存中。缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义。

示意图:

image-20210807124010452

解决方案

1、基础校验

接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;

2、缓存空对象

在持久层没有命中的情况下,对该 key 进行set(key,null)

image-20210807124622823

存在问题
  1. 占用内存空间:value为null 不代表不占用内存空间,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除;

  2. 数据不一致:缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。

    例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。

3、布隆过滤器

在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证是key否存在,如果存在在进入缓存层、存储层。

可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。

布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率删除困难

算法描述

  • 初始状态时,BloomFilter是一个长度为m的位数组,每一位都置为0;
  • 添加元素x时,x使用k个hash函数得到k个hash值,对m取余,对应的bit位设置为1;
  • 判断y是否属于这个集合,对y使用k个哈希函数得到k个哈希值,对m取余,所有对应的位置都是1,则认为y属于该集合(哈希冲突,可能存在误判),否则就认为y不属于该集合。可以通过增加哈希函数和增加二进制位数组的长度来降低错报率。
存在问题
  1. 存在误识别;
  2. 删除困难。

缓存击穿

指一个key非常热门,在不停的扛着大并发的访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞;

key失效:当某个key缓存过期了,会访问数据库来查询最新数据,并且回写缓存。

解决方案

1、设置热点key永不过期
  • 从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。
2、分布式互斥锁

只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。set(key,value,timeout)

两种方案对比

  • 设置热点永不过期:由于key没有过期时间,实际上已经不存在热点key产生的一系列问题了。但是会存在数据不一致的情况,同时代码复杂度会增大;
  • 分布式互斥锁:存在一定隐患,如果在 查询数据库+重建缓存 时间过程,也可能会存在死锁和线程池阻塞的风险,高并发情景下吞吐量会大大降低!但是这种方法能够较好降低后端存储负载,并在一致性做的比较好。

缓存雪崩

缓存层由于某些原因不可用(宕机)或者大量缓存在同一时间段失效(大批key失效/热点数据失效),大量请求直接到达存储层,存储层压力过大导致系统雪崩。

解决方案

1、错开key失效时间

缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生;

2、高可用

可以把缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。利用sentinelcluster实现;

3、多级缓存

采用多级缓存,本地进程作为一级缓存,redis作为二级缓存,不同级别的缓存设置的超时时间不同,即使某级缓存过期了,也有其他级别缓存兜底。

缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。

这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。