如何保证缓存与数据库的一致性

如何保证缓存与数据库的一致性

对于读多写少的场景,为了减缓数据库的压力,提高请求响应速度,我们会引入缓存;而引入缓存之后随之而来的就是数据一致性问题。

谈谈一致性

一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。

  • 强一致性:这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大
  • 弱一致性:这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态
  • 最终一致性:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型

缓存策略

对于缓存有两种常见的方式:Cache-Aside和Write-Through

Cache-Aside

旁路缓存也叫延迟加载,是一种被动的缓存方式,更新数据的时候不会更新缓存,当需要读取数据时,再去更新缓存。

Cache-Aside 读取流程:

  1. 先读取缓存,如果命中缓存,则直接返回缓存数据;
  2. 如果没有命中,则先从数据库读取数据,然后更新缓存,最后返回数据。

Cache-Aside 写入流程:

写入数据时,先更新数据库,然后再删除缓存。

这里大家可能会有个疑问,为什么Cache-Aside模式写入时要删除缓存呢?

让我们来看看下面的例子:

  1. 线程A发起了一个写操作,先更新了数据库;
  2. 线程B也发起了写操作,接着更新了数据库;
  3. 由于网络延时等原因,线程B先更新了缓存;
  4. 最后线程A更新了缓存。

这时,缓存保存的是A线程的数据(老数据),数据库保存的是B线程的数据(新数据),这里就出现了数据不一致的问题,如果用删除缓存的方式,就可以避免这个问题。

Write-Through

直写是一种主动的缓存方式,在数据库上的数据被更新之后,会立即更新缓存。

Write-Through 写入流程:

Write-Through的优点:

缓存与数据库的数据一直是最新的,能保持数据的一致性;

命中缓存的概率要大很多,能减少读库的次数。

缺点:

在多线程情况下可能会出现数据不一致的问题。

如何实现一致性

缓存延时双删

无论是先删除缓存还是先写数据库,都会出现缓存与数据库不一致的情况,可以使用延时双删策略。

  1. 先删除缓存;
  2. 再写入数据库;
  3. 休眠500毫秒(休眠时间根据具体的业务时间来定,一般为读请求的平均耗时+几百毫秒即可);
  4. 再次删除缓存。

删除缓存重试机制

在删除缓存的过程中,如果出现了删除失败,就会出现缓存数据不一致的情况,这时我们就需要引入删除缓存重试机制。

  1. 写请求更新数据库
  2. 因为某些原因,缓存删除失败
  3. 将删除失败的key推送至消息列队
  4. 接受消息列队的消息,获取删除失败的key
  5. 删除缓存重试

读取binlog日志异步删除缓存

虽然上面的方式都可以解决数据不一致的问题,但是会造成业务代码入侵;

我们还可以通过binlog日志的来异步的删除缓存。

作者

ero

发布于

2022-03-05

更新于

2022-06-11

许可协议

评论