缓存与数据库双写一致性问题

1. 场景

缓存架构设计,可以提高系统的读性能,在缓存建立key-value的键值对,减少数据库的压力,通常用于读多写少的场景,流程一般如下:

  • 用户首先查询缓存(如Redis)是否有相关数据;
  • 如果有相关数据,缓存命中,返回查询结果;
  • 如果没有相关数据,则查询数据库,将结果放入缓存,同时返回结果;

2. 缓存更新策略

如果有数据更新,是先操作缓存,还是先操作数据库,这里涉及时序问题;而对于缓存的操作,是删除缓存,还是更新缓存?总结有以下几个方案:

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

其实通常缓存的数据都会设置一个过期时间,如果不要求强一致,那么在过期后,数据删除,后面的查询请求还是会把结果重新放入缓存,数据还是会达到最终的一致性。

2.1 更新缓存 or 删除缓存

这个和业务有关,但有以下情况需要考虑:

  • 如果要更新的数据是通过复杂计算得出的,比如商品价格,要查询商品原价,商品折扣,卖家和平台优惠信息,那还不如直接把缓存删除,否则更新缓存还需要去计算一次;而如果只是删除缓存,顶多增加一次查询命中失败;
  • 如果场景是写多读少的话,缓存被频繁的更新,影响系统性能;
  • 存在线程安全问题,无论先更新数据库,还是先更新缓存,并发情况下都可能导致数据不一致,比如线程1更新数据库,线程2更新数据库,线程2更新缓存,线程1更新缓存;
  • 原子性,更新数据库和更新缓存的操作不能保证在同一个事务内,不能保证原子性(当然删除缓存也是,所以需要补偿方法);

所以一般选择删除缓存。

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

问题流程如下:

  • 更新数据库成功;
  • 删除缓存失败;

之前提到由于不能保证原子性,所以有可能出现这种数据不一致的情况。

解决方案:
重试机制,将删除失败的key加入消息队列,重试直到删除成功,但这样增加了架构难度,所以并不推荐先更新数据库再删除缓存的方案,网上也有说facebook是这种策略,具体根据自己项目情况。

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

问题流程如下,线程1写操作,线程2读操作:

  • 线程1删除缓存;
  • 线程2查询发现缓存不存在,查询数据库得到旧数据,并把旧数据写入缓存;
  • 线程1更新数据库;

这样导致缓存的数据为脏数据,数据不一致;

解决方案:
串行化操作:更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。
一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。
这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。
待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中。
如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。

这种方案有明显的性能问题,串行化会导致系统的吞吐量降低,并且在分布式环境下,基于内存的队列无法解决这个问题,但是也有解决方案,重点是保证同一个数据的读写都落在同一个后端服务上,并且做到在数据库层面是串行的,详情参考:

https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=404202261&idx=1&sn=1b8254ba5013952923bdc21e0579108e&scene=21#wechat_redirect


3. 总结

总得来说,业界推荐删除缓存的策略,但是无论先更新数据库,还是先删除缓存,都有弊端,需要做补偿,在不要求强一致的情况下,超时机制应该是最合适的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值