了解双写一致性方案

解决方案

  1. 读请求写请求串行化执行
  2. 先更新数据库,再更新缓存
  3. 先删除缓存,再更新数据库
  4. Cache Aside Pattern

一、读写请求串行化

串行化执行,保证数据一致性,但是会大幅降低系统吞吐量,不采用

二、先更新数据库,再更新缓存

当数据库数据更新时,先更新数据库中数据,再更新对应的缓存数据。这个方案主要有两个问题。

1.问题1

如果同时有A、B两个请求进行更新操作,那么该方案可能会有以下情况:

  1. A请求更新数据库
  2. B请求更新数据库
  3. B请求更新缓存
  4. A请求更新缓存

本来B请求更新后的数据时最新数据,但是A更改请求却晚于B请求,导致了缓存中是旧数据,数据库与缓存的数据不一致。而且如果缓存没有设置过期时间,那么缓存中的永远是脏数据。

2.问题2

缓存中缓存的数据有时候并不一定是从数据库中读取出来的,也有可能是多张表进行计算得出的结果。这个计算过程本身就是耗时的,而且这个缓存数据并不一定是热点数据(访问次数不多、频率不高),这时如果每次修改数据库值,就要重新进行一次耗时的计算,这会使得更新缓存所花费的代价得不偿失(花费时间、资源多,访问次数却很少)

三、先删除缓存,再更新数据库

因为更新缓存的代价可能会很大,所以又引出了删除缓存的策略

1.为什么先删除缓存

如果先跟新数据库,在删除缓存,那么可能会造成以下问题:

  • 数据库更新成功,但是缓存删除失败,从而造成数据不一致

2.问题1

有两个请求,A请求更新,B请求查询,那么该方案可能会出现以下情况:

  1. A请求更新,所以先删除缓存
  2. B请求查询,缓存已经被删除
  3. B请求查询数据库,得到旧的值
  4. B请求将旧的值返回并写入缓存
  5. A请求将新的值写入数据库

可以看到,这种情况下,缓存中出现了脏数据。而且如果缓存没有过期时间,那么脏数据将一直存在。

问题1解决方案——延时双删策略

操作流程:

  1. 先删除缓存
  2. 写入数据库
  3. 休眠1秒,再次删除缓存。这样就可以将在1秒内所造成的脏数据再次删除。

休眠时间的确定: 应该评估项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

3.问题2

延时双删第二次的删除失败怎么办(引出Cache Aside Pattern)

四、Cache Aside Pattern

思想: 先更新数据库,再删除缓存

对于读写请求,定义了三种情况

情况 描述
失效 先从cache中读数据,缓存中不存在,则从数据库中取数据,成功后写入缓存并返回
命中 cache存在数据,直接从缓存读取返回
更新 先把数据更新到数据库,然后删除缓存

1.问题1

假设A请求查询,B请求更新:

  1. 缓存刚好失效
  2. 请求A查询数据库,得一个旧值
  3. 请求B将新值写入数据库
  4. 请求B删除缓存
  5. 请求A将查到的旧值写入缓存

这种情况要发生,必须是B写入数据库的耗时小于A请求查询的耗时(步骤3耗时小于步骤2),才有可能使得步骤4优先发生于步骤5,但是读操作是远远快鱼写操作的,所以这种情况非常难发生

解决方案

方案1: 设置缓存过期时间

方案2: 采用异步延时双删策略,保证读请求完成后,再次进行缓存删除操作

2.问题2

第二次删除缓存失败

解决方案

提供一个保障的重试机制

方案1:
  1. 更新数据库数据
  2. 缓存因为种种问题删除失败
  3. 将需要删除的key发送至消息队列
  4. 自己消费消息,获得需要删除的key
  5. 继续重试删除操作,直到成功
    了解双写一致性方案
    (网图侵删)

弊端:对业务代码造成大量入侵

方案2:

启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。

  1. 更新数据库数据
  2. 数据库会将操作信息写入binlog日志当中
  3. 订阅程序提取出所需要的数据以及key
  4. 另起一段非业务代码,获得该信息
  5. 尝试删除缓存操作,发现删除失败
  6. 将这些信息发送至消息队列
  7. 重新从消息队列中获得该数据,重试操作
    了解双写一致性方案(网图侵删)