数据库和缓存一致性

一、思考

今天程序过程中突然想到了一个问题,怎么保证redis缓存和mysql数据库中的数据相同(一致性)。即在更新数据时怎样保证数据库和redis缓存始终相同。从理论上讲,给缓存设置过期时间是保证最终一致性的解决方案。这种方案下,所有写操作以数据库为准,对缓存做最大努力即可。下面介绍的是不依赖于给缓存设置过期时间的更新数据的方案。

首先必须明确一点,所有的数据都是必须以数据库持久化的数据为主,缓存是为了加快获取数据的方式,所以无论怎样保证一致性都必须保证数据库中的数据是逻辑正确的。

二、几种常见的更新(写)策略

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

2.1 先更新数据库,再更新缓存

如下的不一致的情况:

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

image.png

如图,可能是网络原因或者是更新数据大小写入操作耗时等问题,导致先更新的最后才写入到缓存,从而导致 缓存与数据库不一致。此场景不易被消除,所以此中方式不可取。

业务场景不可取原因:

  1. 若存在大量且快速的更新操作,那么还未来得及读取缓存,缓存数据就又被更新了,这样就有很多更新是不必要的,浪费性能。

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

如下的不一致的情况:

  1. 线程A先删除缓存;
  2. 线程B读取缓存,读取不到;
  3. 线程B到数据库读取到旧值;
  4. 线程B读取到的旧值写入缓存;
  5. 线程A更新数据库;

image.png
同样在这种情况下,若是采用了读写分离的策略,则有如下不一致的情况:

  1. 线程A先删除缓存;
  2. 线程A更新数据到写数据库;
  3. 线程B读取缓存,获取不到;
  4. 线程B从读数据库读取获取到旧值;
  5. 线程B把旧值写入缓存;
  6. 数据库完成主从复制,读数据库更新了值

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

如下的不一致的情况:

  1. 缓存刚好失效;
  2. 线程B读取缓存无效;
  3. 线程B读取数据库旧值;;
  4. 线程A更新数据库;
  5. 线程A删除缓存;
  6. 线程B把旧值写入缓存;

image.png

这种情况相对来说少一些,因为一般写数据库的操作更加耗时,并且还得线程A一次完成两步,所以此方案是更容易接受的。

三、解决方案

3.1 延时双删

基本的过程分为三步:

  1. 先删除缓存;
  2. 更新数据库;
  3. 过一定时间后再删除缓存一次(1~2s)

存在的问题:
这是一次同步的操作的话,这个1~2s会大大的降低系统的性能。所以可以就爱能第二步的删除做一个异步删除。
但是如果第二次删除失败呢??

3.2 二次删除失败重试

  1. 更新数据库数据;
  2. 数据库会将操作信息写入binlog日志当中;
  3. 订阅程序提取出所需要的数据以及key;
  4. 另起一段非业务代码,获得该信息;
  5. 尝试删除缓存操作,发现删除失败;
  6. 将这些信息发送至消息队列;
  7. 重新从消息队列中获得该数据,重试操作;

备注说明:上述的订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。另外,重试机制,博主是采用的是消息队列的方式。如果对一致性要求不是很高,直接在程序中另起一个线程,每隔一段时间去重试即可,这些大家可以灵活自由发挥,只是提供一个思路。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值