缓存和数据库的一致性问题 ----COOKIE

原始的读写数据库的方式:

读:    

通常来说,在我们的系统中会把数据永久保存在DB中,并且冗余一份数据在缓存中。读请求优先从缓存读取数据,没有再从DB读取,如下图:

写: 

一、先写缓存再写DB

如果第一步写缓存失败,直接返回,无影响。

如果缓存写成功,DB写失败,此时如果不清除缓存中已写入的数据,则会造成数据不一致(缓存中是新值,DB中是旧值)。

如果增加清除缓存的逻辑,那么清除操作又失败了该如何处理?

二、先写DB再写缓存

如果DB写入失败,直接返回,无影响。

如果DB写入成功,缓存写入失败则会造成数据不一致(即DB中是新值,缓存中是旧值)。

如果重试写入缓存,那重试也失败该如何处理?

 

解决方案: 

 1.利用类似于分布式锁的标志位,进行读写关联;

  • 写请求流程:

 

 

   写请求流程:

     如上图,每次处理写请求时,将会经过如下几个步骤:

 

  1. 首先针对要写入的数据设置一个状态,失败则结束,成功则转2。
  2. 如果设置状态成功,则直接清除缓存,失败则解除状态并结束,成功则转3。
  3. 清除缓存后,再写入DB,失败则解除状态并结束,成功则转4。
  4. DB写入成功以后,把新值回填缓存,失败则解除状态并结束,成功则转5。

         回填成功,解除状态并结束。

 

 

读请求流程: 

如上图,每次处理读请求时,将会经过如下几个步骤:

 

  1. 直接从缓存读取数据,成功则结束,失败则转2。
  2.  从DB读取数据,失败则返回,成功则转3。
  3. 根据从DB读取到的数据判断该数据对应的状态,如果没有状态,则回填缓存并结束,如果有状态,则直接结束。

 

总结来说就是我们通过一个状态把读写请求关联起来,这里先不讨论这个状态的实现细节以及各种容错,比如说解除失败以后怎么处理。

 

解决疑问: 为甚麽要设置状态位呢?

大家设想一下这种情况: 

  数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数      据库,查到了修改前的旧数据,放到了缓存中。随后数据变更的程序完成了数据库的修改。

 其实这里写操作时: 进行回填缓存可以去掉这一步骤,有时反而并不会使效率提高;

然而,在读操作的时候先要进行判定标志位,然后再进行回填缓存,也就是说: 回填缓存和 数据库进行写操作 这两种操作是不能同时进行的;

 

 

缓存请求队列: 

更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。

一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。

这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。

待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中。

如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值