redis的缓存更新、缓存穿透、缓存雪崩、缓存击穿问题| 青训营

80 阅读6分钟

本篇笔记通过青训营redis课程的学习以及自己上网查找的资料,整理了redis缓存的使用与缓存更新、穿透、雪崩、击穿问题的解决方案。

0.为什么用缓存
  • 好处:

(1)降低后段负载

(2)提高读写效率,降低响应时间

  • 坏处:

(1)数据一致性成本

(2)代码维护成本、运维成本

1.缓存更新
1.1 缓存更新问题、解决思路

问题:未来保证缓存和数据库的一致性,需要考虑缓存的更新问题。有三种缓存更新策略,各有优劣如下图所示。对于低一致性需求,使用redis自带的内存淘汰机制;高一致性需求则需要结合主动更新和超时剔除两种方法。

image.png (1)在主动更新策略中,选择更新数据库同时更新缓存的方案。主动更新策略可分为三种,其中第二种服务比较复杂很少有提供;第三种异步机制比较高效,但是维护复杂需要实时监控缓存数据的变更,并且变更不及时会出现不一致的问题;因此第一种更新数据库同时更新缓存的方案是最常用的:

image.png (2)在更新数据库同时更新缓存策略中,又需要考虑三个问题:

image.png 对于问题1,很明显删除缓存更高效,因为数据库频繁更新的时候如果采取更新缓存的策略会带来频繁的操作;而更新数据库的时候删除缓存,客户端查询时再更新,可以在频繁的数据库更新时降低对缓存的读写。

对于问题2,需要依照具体的系统构建场景来决定。

对于问题3,下面进行更详细的讨论。

(3)选择先更新数据库再删除缓存。接下来分析更新数据库、删除缓存两者的先后会产生的异常情况,谁出现的可能性更小,则选择谁。

① 先删除缓存,再更新数据库。假设现在有两个线程,线程1要更新数据因此执行先删除缓存再更新数据库;与此同时有一个线程2来进行查询;刚好查询发生在线程1刚删除完缓存还未更新数据库,此时线程2没有命中缓存则查询数据库拿到了旧值10,并用此值10更新了缓存,而线程2结束操作后线程1才开始更新数据库,此时数据库中数据假设更新为20,结束操作。可以看到数据库和缓存之间出现了不一致问题。

  • 发生概率:在准备更新数据库的过程中,另一个线程发生了查询数据库与写入缓存。由于更新数据库的时间相对查询数据库、写入缓存来说慢很多,因此很有可能发生。

② 先更新数据库,再删除缓存。假设现在有两个线程,线程1查询缓存未命中,先从数据库中取到数据再准备更新缓存;与此同时有一个线程2来执行更新,先更新数据库再删除缓存;刚好更新发生在线程1刚查询完数据库值为10准备写入缓存的过程中,此时线程2更新了数据库的值为20,而线程2结束操作后线程1才开始更新数据库,此时数据库总写入了之前查到的10,结束操作。剋看到数据库和缓存之间出现了不一致问题。

  • 发生概率:在准备写缓存的过程中,另一个线程发生了数据库的更新与删除缓存。由于写缓存的时间相对数据库更新来说快很多,因此发生概率很小。

image.png 综上所述,我们通常选择先更新数据库,再删除缓存的主动更新策略。

2.缓存穿透
2.1缓存穿透问题、解决思路
  • 问题:缓存穿透是指客户端请求的数据,同时不在缓存、数据库中,缓存永远不会被更新生效,数据全部被打倒数据库。
  • 方案:有两种。一种是缓存空对象,另一种是布隆过滤。我们在这里使用缓存空对象来解决缓存穿透问题。

image.png

3.缓存雪崩
3.1缓存雪崩问题、解决思路
  • 问题:分为两种,第一种是redis中大量key同时失效,导致大量数据都打到了数据库;第二种是redis宕机了,导致所有访问都直接打到了数据库
  • 方案:对于第一种问题可以为每个key添加一定范围内的随机值;对于第二个宕机问题可以使用集群、限流、多级缓存等策略。

image.png

4.缓存击穿
4.1缓存击穿问题、解决思路

image.png

  • 问题:缓存中一个被高并发访问,且缓存重建任务较复杂的key突然失效了,大量请求会打到数据库。
  • 方案:一种使用互斥锁限制多个线程在重建缓存前同时访问;另一种是对会发生缓存击穿的热点key不设置TTL长久保存在缓存中,而设置一个逻辑过期时间。详细来说就是每个请求来时都会命中缓存,如果逻辑过期时间到了,会同样像数据库中请求新的缓存,但旧的缓存在逻辑过期后并不删除,如果别的线程此时访问会返回给他旧的缓存数据。
  • 逻辑过期:但每个线程查询缓存如果发现未命中,按照入行所述就都会去数据库中请求数据并返回旧数据,这样也是频繁访问数据库呀?这里要用到互斥锁,线程1第一次发现逻辑过期,就会开启一个互斥锁,然后交给另一个线程2执行数据库更新和关闭互斥锁的操作;此时线程1直接返回旧的缓存数据,除了12其他线程此时访问也会发现逻辑过期,会试图获取互斥锁但是失败(因为要等线程2执行完由其释放),如果获取锁失败则直接返回缓存中现有旧数据。这样就完成了逻辑过期的功能,具体见下图:

image.png 两种方案的选择其实就是在一致性和可用性之间的权衡:

image.png

End.