Skip to main content

Binlog和MQ如何解决缓存一致性?

作者:程序员马丁

在线博客:https://open8gu.com

note

大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。

回答话术

Binlog(Binary Log)是 MySQL 数据库的一种日志文件,它记录了所有对数据库的修改操作,例如插入、更新、删除等。MQ(消息队列)是一种用于在应用程序之间传递消息的通信机制。

使用 Binlog 配合 MQ 可以实现缓存和数据库之间的一致性解决方案。

  1. 将数据库的变更记录写入 Binlog:当数据库发生写操作时,MySQL 会将这些变更记录到 Binlog 中。
  2. 使用 Canal 来监听和解析 Binlog 中的变更记录。
  3. 将变更记录发送到 MQ:一旦 Binlog 中的变更记录被解析,将其转化为相应的消息,并发送到 MQ 中。
  4. 消费 MQ 消息更新缓存:另一个消费者从 MQ 中获取消息,然后根据消息内容执行相应的操作,可能是更新缓存,也可能是执行其他业务逻辑。

这种方案是我认为最终一致性最为值得尝试以及使用的。但是有一句话说的是没有绝对合适的技术,只有相对适合的技术,这种方案实现是也存在一些技术问题,详情见文末:方案注意事项。

问题详解

1. Binlog 配合 MQ 缓存一致性

Binlog 配合 MQ 完成缓存一致性时序图如下:

如果是扣减库存的方案,比如说你将列车余票扣减为 16,但是同时又有一个请求将列车余票扣减为 15,这个时候,扣减为 15 的这个请求先到消息队列执行,将缓存更新为余票 15,但是随之而来的是第一个请求余票为 16,会将缓存余票为 15 给覆盖掉。

类似于这种逻辑,会存在一些数据一致性的问题,需要我们通过其它技术手段完善,比如数据库添加版本号,或者根据最后修改时间等技术规避这些问题。

另外,如果在写入数据库余票 16 前,同时有个查询请求,也会存在数据库不一致问题。比如在写入数据库余票 16 前,将数据库余票 17 获取到,然后等消息队列更新到缓存余票 16 后,再将数据库余票 17 更新到缓存。

这种出问题的概率比较小,因为跨的周期太长了。也是类似于存在一个很小周期的数据不一致性。

2. 方案注意事项

需要额外注意的是,因为 Binlog 监听中用到了消息队列,就不得不考虑重复消费问题,需要添加幂等注解保证仅消费单次。

如果是更新库存数量,比如库存加减,不要再去数据库查询最新库存,而是通过 Redis 提供的自增命令即可,简单且高效。

如果是更新车站信息,例如修改列车信息等类似数据,可能会面临并发操作中的 ABA 问题。为了更好地理解,我们可以举个例子:假设我们将复兴号的发车时间从之前的 12:00 修改为 16:00,但在短时间内发现这个更改是错误的,因此又将 16:00 修改为 16:30。

这种情况下,存在一个可能性,即后一次修改 16:30 的请求先执行,然后再执行 16:00 的变更,导致数据不一致的情况发生。发生这个问题的原因在于投递到消息队列后,默认消息是无序的。

针对这种问题背景,我们可以提出两种解决方案,同时对其进行优化和补充说明。

2.1. 顺序消息队列解决方案

针对那些不经常变更的数据,可以使用消息队列来保证修改变更的顺序性。通过将每次修改操作作为一个顺序消息发送到消息队列中,可以确保消息按照发送的顺序被处理,从而避免了 ABA 问题的发生。

然而,需要注意的是,顺序消息的解决方案也存在一定的风险。如果某个列车数据异常导致消息阻塞,可能会影响整个消息队列的处理速度和稳定性。

2.2. 增加版本号解决方案

在进行修改操作时,先判断当前版本号是否小于要修改的版本号,只有在当前版本号小于目标版本号的情况下才进行修改。

通过增加版本号,可以有效避免并发修改引起的数据不一致问题。然而,这种方案需要对现有的数据库和缓存结构进行改动,可能会带来一定的执行成本和复杂性。

2.3. 个人推荐

我个人倾向于推荐第二种解决方案,即增加版本号。这种方案相对稳妥且高效,可以在保证数据一致性的同时降低风险。然而,具体选择哪种解决方案还取决于您的实际需求和系统环境。请综合考虑各种因素并做出适合您情况的选择。