如何保证数据库和缓存数据一致

一般来说生产环境中,我们为了保证数据响应的速度,会将数据保存在数据库中,但是会将部分数据(一般是最近被访问的数据)备份在缓存中。用来避免频繁的IO导致的性能下降。

数据的不一致

而现在我们大多使用读写分离的行为,既然数据被保存在了两个地方,在数据更新的时候就可能导致数据的不一致。如何保证数据库和缓存数据的一致。

简单的处理

目前关于简单保证缓存一致性的方案主要有下面几个

  1. 先写缓存再写DB\先写DB再写缓存

使用这种方式,整个流程被分为了下面样子

第一阶段
第二阶段
修改数据
修改DB/cache
修改cache/DB

这样操作可能出现的情况:

  • 第一阶段失败的时候,将不会产生影响。
  • 第二阶段更新失败会产生cache和DB数据的不一致。
  1. 先写DB再删除缓存。

使用这种方式的考虑在于,每次修改数据就去更新缓存的数据,但是此时缓存中的数据可能并没有被使用。而删除缓存可以保证它到需要被使用的时候再重新计算。这种操作就是数据的懒加载的方式。

  1. 先写DB再写缓存,缓存添加过期时间。

使用这种方式,可以保证在数据更新成功,去更新缓存失败的时候,缓存一段时间后因为超时而去同步数据库中的数据,从而完成数据的最终一致性。在简单的处理方式中国,这一种应该是最接近问题解决的方案。但是这种方案的问题在于在缓存过期之前,缓存和数据库中数据不一致,会导致短时间的脏数据。


为操作设置一个任务

一种方案是将每个操作作为一个任务压入一个共享的队列中,使用队列来保证先后顺序。
写入数据的时候大概流程

  1. 当我们写数据的时候,根据数据的标识,设置一个操作的任务。
  2. 首先删除缓存,然后将更新操作压入队列中。
  3. 后续工作线程中取出任务执行数据更新操作。
    而我们读取数据的流程
  4. 当我们读数据的时候,发现数据不存在,我们设置一个更新缓存的任务。
  5. 然后将任务压入队列中。
  6. 更新缓存操作。

整个流程可以归纳为下面的过程

数据存在
此数据任务存在,等待
数据不存在
此数据任务不存在
更新数据
更新缓存
写操作
更新数据任务
删除缓存
读操作
读取缓存数据
返回数据
创建更新缓存任务
任务队列
更新数据库
读取数据库

这个流程实际上可以发现,解决方案其实是把读写操作作为了一个串行的操作。这样操作可以保证缓存和数据库中数据的强一致。
但是存在几个问题。

  • 使用串行操作会导致读效率的降低。
  • 因为把任务都压入一个队列中,当头部任务执行时间过长将会导致后续任务的延迟。

优化的策略主要针对效率和超时

  • 为了避免效率过低,可以开放多个队列并行处理任务,这里需要保证同样的数据标识的任务可以分配到相同的队列中。
  • 设置超时,当查询任务达到一定时间后仍无法获得结果时,应允许任务直接去数据库中读取数据返回(及时为旧值)
为数据设置状态

另外一种方案通过为数据设置状态来标识数据此时是否可用。整个流程可以描述为下面内容。

写数据

  1. 写数据前尝试以数据标识为key设置其锁定状态。
  2. 状态设置失败则证明有其他任务在更新数据,直接失败或者重试
  3. 状态设置成功,开始清除缓存。
  4. 缓存清除失败,解除状态,同时返回失败或重试
  5. 缓存清除成功则将数据写入数据库。
  6. 写数据失败则,解除状态,返回失败或重试
  7. 写数据成功则,解除状态返回成功

整个流程总结下来是如下内容

设置状态失败
失败
失败
写操作
设置数据状态
更新失败,重试
清除缓存
移除状态,更新失败,重试
写数据
移除状态
数据更新完成

读数据

  1. 读数据前尝试以数据标识为key获取其锁定状态。
  2. 获取状态为锁定,直接失败或者重试
  3. 获取状态成功,开始读取DB数据。
  4. 读取DB数据失败,解除状态,同时返回失败或重试
  5. 读取DB数据成功,设置缓存数据。
  6. 设置缓存数据失败,解除状态,返回失败或重试
  7. 设置缓存数据成功则,解除状态返回成功

整个流程总结下来是如下内容

数据被锁定
数据正常
失败
失败
读操作
获取数据状态
更新失败,重试
读取数据库数据
移除状态,更新失败,重试
更新缓存数据
移除状态
数据更新完成

为数据设置状态的处理方案,其实和Redis分布式锁的思想是一致的。通过获取数据的锁来判断数据此时是否可以被更新或者读取。
使用类似读写锁的操作来保证并行读和串行读写。

这种情况已经尽量满足一致性的要求,当然这里面还有一些东西需要注意。

缓存清理的时机

写数据的时候,在设置了数据状态之后就进行缓存清理工作。假如此时缓存清理成功后,在更新数据失败。此时会导致缓存数据为空,然后不得不去数据库再次去获取旧的内容。
而假如我们更新时候将缓存清理推后到数据更新之后,可以保证在数据更新失败情况下缓存无需进行额外操作。但此时会面临两个问题,在更新数据和清理缓存中短暂时间内“脏数据”的存在,其次在更新数据成功而清理缓存失败的情况下导致数据库中数据和缓存中数据的不一致,在没有设置缓存时间的情况下,缓存无法自动修正。

更新缓存的操作

当一个数据被更新的时候,针对此数据读取会被阻塞起来,多个请求可能都在等待请求数据。当次数据更新完成后,
为了避免多个请求重复的想数据库请求此数据。可以在获取到此数据的锁定状态后,再次向缓存中验证数据是否存在。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大·风

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值