Redis 缓存实战——缓存、数据库一致性问题分析与解决方案

引言

缓存与数据库一致性的问题自从出现了缓存概念后就一直被提及,它是一个缓存方案的先天缺陷,只要存在缓存,就势必会讨论缓存与数据库一致性的问题。

一致性问题还广泛存在于各种分布式存储场景中,如主从一致性等等。

本篇博客讨论和整理了缓存、数据库一致性问题的一些思路,在实际的缓存业务场景中,可以对技术实现的起到一定指导作用。

一、为什么会出现一致性问题

缓存作为应用程序与数据库之间的数据存储池,主要作用就是热数据备份的作用,它的主要目的就是提高热数据的查询效率。因此,在读取缓存方面,普遍的做法是没有疑问的,基本都是按照如下流程执行:

在写数据方面,却存在很多问题,其中一点就是——如何保证数据库的数据与缓存的数据保持一致。

二、强一致性、弱一致性、最终一致性

在一致性问题上,分为事务一致性和数据一致性,而缓存、数据库一致性很明显就是数据一致性。

其实只有两类数据一致性,强一致性和弱一致性。除此之外,所有其他的一致性都是弱一致性的特殊情况。

2.1 强一致性与弱一致性

强一致性又叫线性一致性,顾名思义,就是在多个数据源之间以同步的方式复制数据。

弱一致性,即复制是异步的。在时间中,我们通常使一个从库是同步的,而其他的则是异步的。如果这个的从库出现问题,则使另一个异步从库同步。

这样可以确保永远有两个节点拥有完整数据。主库和同步从库,这种配置成为半同步。

2.2 最终一致性

最终一致性表示不一致的状态可能会出现,但这也只是暂时的,不同的数据源会在未来某个时点保持同步。

而为缓存设置过期时间可以有效的保证最终一致性。

三、缓存更新策略

解决缓存、数据库一致性问题的方法,无非就是在更新数据库时尽可能的减少不一致的情况出现。

但只要存在缓存,就无法保证数据的强一致性,选择不同的缓存更新策略,也只是在弱一致性的前提下,尽可能保证数据的同步,或达到最终一致性。

从理论上来讲,共有四种更新策略:

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

一般情况下,为了保证数据的安全,都会第一时间将数据更新到数据库中,所以先操作缓存的方式往往不被采纳。

对于先更新数据库的情况,到底是选择更新缓存还是删除缓存呢?

3.1 先更库,更缓存

结论是,这种方案普遍不被采纳。

第一点原因,线程安全性,如果同时有请求A 和 请求 B 都要更新数据,那么会出现如下情况:

  1. 线程 A 更新了数据库
  2. 线程 B 更新了数据库
  3. 线程 B 更新了缓存
  4. 线程 A 更新了缓存

本来按照先后顺序,A的更新操作在 B 之前,最终也应该是缓存中存放 B 更新的数据,但是由于一些特殊原因,导致后更新数据的 B 先于 A 更新了缓存,就导致了脏数据。

第二点原因,如果是一个写多读少的场景,采用这种方案由于每次更新数据都要去更新缓存,但从缓存中查询的次数却寥寥无几,不仅没有起到提高查询性能的目的,反而还极大的浪费性能。

3.2 先更库,删缓存

该方案同样会导致数据不一致。例如,同时有一个请求 A 查询数据,另一个请求 B 更新数据,会产生如下情形:

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

上面的情况是有可能发生的,它产生的原因是由于 A 将取得的旧数据(在 B 更新完数据并执行了删除缓存操作之后)又放入到了缓存,即缓存中的数据是 A 之前取得的旧数据。

但是这种情况发生的概率很低,这是因为写库操作的速度要远低于读库的速度。正因为如此,请求 B 在将新数据写入到数据库后才会大概率在请求 A 将旧数据写入缓存之后执行删除缓存操作。

即大概率发生的情形应该是:

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

由于写库操作要更加缓慢,因此,大概率会在最后,在其他读操作的写入缓存执行后再将缓存删除,从而保证数据的一致性。

另外,更新缓存实际上可以看做是先删除再添加的两步操作,那么对于删缓存的操作,无非就是将添加缓存安排到了下次查询的时候,这也在查询情况较少的时候避免了频繁更新缓存的尴尬。

因此,先更库,再删除缓存是一种较为可行的解决一致性问题的方案。

四、先更库再删缓存的其他问题

如果缓存删除失败了怎么办?

的确,如果更新了数据库之后,由于特殊原因,导致缓存删除失败,缓存中依然保留了旧数据,那么同样存在不一致的问题。

这个时候,我们需要为业务系统增加全局的缓存失效时间,这样可以保证不一致也只是暂时的,即达到最终一致性的效果。

或者使用下面的解决方案,使用消息队列保存数据库更新后失败的缓存删除操作,再某一时点进行重试,其中 binlog 订阅程序可以选择现成的MySQL binlog订阅中间件 “canal”。

鸣谢:

缓存与数据库的一致性问题解析

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页

打赏

圣斗士Morty

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者