迎着风跑  

缓存与数据库一致性

#1. Cache Aside Pattern

标准的方案,facebook 就是使用这种方式。

核心概念说明
失效 应用程序先从 cache 取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
命中 应用程序从 cache 中取数据,取到后返回。
更新 先把数据存到数据库中,成功后,再让缓存失效。

#读流程:

步骤说明
1 读缓存,命中则直接返回
2 如果没命中,读数据库
3 更新缓存

#写流程:

步骤说明
1 更新数据库
2 删缓存,使缓存失效

#2. 双写并发问题

Cache Aside Pattern 方案能解决 双写并发 问题:

双写并发:简而言之,张三的写操作 一旦和 李四的写操作 交织在一起,就会导致缓存中的数据错误。

#用户操作数据库中的值缓存中的值
1 张三-1 更新数据库 10 -> 20 10
3 李四-1 更新数据库 20 -> 30 30
2 李四-2 删除缓存 30
4 张三-2 删除缓存 30
7 最终 - 30

#3. 读写并发问题

Cache Aside Pattern 方案能解决不了全部的 读写并发 问题:

读写并发:简而言之,张三的写操作 一旦和 李四的读操作 交织在一起,就会导致缓存中的数据错误。

一部分的 读写并发 问题,Cache Aside Pattern 方案能解决。例如:

#用户操作数据库中的值缓存中的值
1 李四-1 读缓存,没命中 10
2 张三-1 更新数据库 10 -> 20 10
3 张三-2 删除缓存 10
4 李四-2 读数据库 20
5 李四-3 更新缓存,同步数据 20 20
6 最终 - 20 20

但是,如果是下面这样的交织时序,Cache Aside Pattern 方案也无能为力:

#用户操作数据库中的值缓存中的值
2 李四-读-1 读缓存,没命中 10
3 李四-读-2 读数据库 10
4 张三-写-1 更新数据库 10 -> 20
5 张三-写-2 删除缓存 20
6 李四-读-3 更新缓存 20 10
7 最终 - 20 10

#4. Cache Aside Pattern 方案总结

这个方案足够简单,容易理解,容易实现。只是面对『部分读写并发问题无能为力』,不过,实际上出现这种概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。

#5. Cache Aside Pattern 方案的改进

TIP

这些改建方案不一定用得上。原因在于:

  1. 有数据不一致的窗口期,这是可接受的。
  2. 改进方案虽然改进了问题,但是同时带来了复杂性。

#方案 1:延迟删除

将写操作的『删除 Redis』操作改为异步的延迟删除。例如:更新完数据库,1 秒钟之后再删除缓存。

这种情况下,读写并发造成的数据不一致问题最多也就存在 1 秒。

这个改进方案的问题在于:你要延迟多久?延迟的时间短了没有解决读写并发问题;延迟的时间越长不一致隐患就越大。

当然,在一致性要求不是那么高的情况下,有 3、5 秒的窗口期数据不一致很正常。

#方案 2:借助消息队列,将删存缓存的工作委托给第三方

  • 读数据的人,在发现缓存中没有数据时,不再由他自己来刷新缓存,而是由『别人』来刷新;

  • 写数据的人,在更新完数据库之后,不再由他自己来删除缓存,而是由『别人』来删除;

简单来说:『别人』先查后刷,查刷一体 。

分析:『别人』是串行化接收、处理消息,在更新缓存时,他是先读 DB,再写 Cache ,这个过程中是没有『其它的别人』插入的。

 

posted on 2021-12-08 20:13  迎着风跑  阅读(197)  评论(0编辑  收藏  举报