数据库与缓存双写一致性方案

从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。但是这种方案会有一段时间(设置的过期时间)内数据库与缓存不一致的情况,以下几种更新策略不依赖过期时间:

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

1. 先更新数据库,再更新缓存

  • 在多线程情况下会导致脏数据的产生:A和B两个线程中A先更新了数据库后B更新了数据库,然后在一些影响下导致A在B更新缓存之后才执行的更新缓存,导致数据库存的是B的最新数据而缓存存的却是A的脏数据。
  • 如果缓存的是冷数据(读的场景比较少相对于写来说),则会造成很大的资料浪费(尤其是在缓存的数据是由数据库的值经过一系列复杂计算后得出的)。

2. 先删除缓存,再更新数据库

这种情况下如果有请求A和B,A先删除缓存,B进行查询发现没有缓存则查询数据库旧值,并把旧值写入缓存,然后A再更新数据库;这样就导致不一致的情况出现。
采用延时双删来解决此问题:先执行删除,然后更新数据库,更新之后等待一会儿(根据具体业务决定)再执行一次缓存的删除;一般通过起一个异步线程来执行删除避免延时导致吞吐量的下降。
如果此时第二次缓存删除失败了怎么办?

3. 先更新数据库,再删除缓存

多线程情况下同样会有问题:与方案二的问题一致,查询请求先于更新请求获取数据库数据,更新请求更新完数据并删除缓存之后,查询请求将查询的旧数据写入缓存,导致不一致的情况发生(读请求执行慢于写请求概率较小)。
这种情况下就需要保证在读请求完成之后再执行删除缓存的操作。如何保证?

缓存删除失败的情况

要确保在数据库更新后,缓存的删除是成功的,需要提供缓存删除失败的重试机制:
有两种方案:

  • 借助MQ,将需要删除的key放入消息队列,消费者获取需要删除的key进行删除,直至成功为止。(该方案对业务代码有侵入?)
  • 启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据(mysql中有现成的中间件叫canal)。

引申1:第三步将需要删除的key放入MQ的操作失败怎么办?如何保证数据库的更新与放入MQ的操作的原子性

参考链接

posted @ 2021-06-12 17:03  Abserver  阅读(412)  评论(0)    收藏  举报