数据库和缓存双写一致性

数据库与缓存双写可以有4种顺序: 更新缓存->更新db更新db->更新缓存删除缓存->更新db更新db->删除缓存

前两种顺序有显著的缺陷:

  1. 后更新的数据源失败,因为无法将先更新的数据源回滚(redis)或者不知道该不该回滚(超时)导致数据不一致;
  2. 多并发(大于两个并发请求,不一定需要高并发)场景下,因为双写不是原子操作,就有可能发生数据不一致;
  3. 读少写多的场景下,大量缓存一直在被更新,却没有被读,一直在做无用功。

后两种顺序也存在在多并发数据场景下数据不一致的情形,但是它们都可以通过 异步延迟双删+失败删除重试机制 解决,其中更新db->删除缓存 发生不一致的概率更低,所以应该首先最后一种顺序实施双写。

首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用。在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作。

img

双写可分为:更新缓存->更新db更新db->更新缓存删除缓存->更新db更新db->删除缓存 四种策略。

并发事务时,前两种策略不仅存在数据不一致的,而且在写多读少的场景下会白白消耗缓存的性能(因为数据还没被读就又被更新了,尤其是在更新的缓存需要通过复杂计算才能得到时,这种消耗更加严重)。后两种策略情况相似,只是更新db->删除缓存相较于删除缓存->更新db发生数据不一致的概率更低(因为只有在写操作先于读操作完成才会不一致,而一般来说一个写操作是要比读操作慢的),为防止不一致发生,它们都采用异步延迟双删+删除重试机制的策略,下面详述常见删除重试机制

重试机制:方案一

img

流程如下所示 (1)更新数据库数据; (2)缓存因为种种问题删除失败 (3)将需要删除的key发送至消息队列 (4)自己消费消息,获得需要删除的key (5)继续重试删除操作,直到成功 然而,该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。

重试机制:方案二

img

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

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

参考链接:

分布式之数据库和缓存双写一致性方案解析

版权

评论