如何利用事务消息实现分布式事务?
什么是分布式事务?
- 消息队列中的“事务”,主要解决的是消息生产者和消息消费者的数据一致性问题。
- 如果我们需要对若干数据进行更新操作,为了保证这些数据的完整性和一致性,我们希望这些更新操作要么都成功,要么都失败。
- 至于更新的数据,不只局限于数据库中的数据,可以是磁盘上的一个文件,也可以是远端的一个服务,或者以其他形式存储的数据。
- 一个严格意义的事务实现,应该具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特性。
- 原子性,是指一个事务操作不可分割,要么成功,要么失败,不能有一半成功一半失败的情况。
- 一致性,是指这些数据在事务执行完成这个时间点之前,读到的一定是更新前的数据,之后读到的一定是更新后的数据,不应该存在一个时刻,让用户读到更新过程中的数据。
- 隔离性,是指一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对正在进行的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性,是指一个事务一旦完成提交,后续的其他操作和故障都不会对事务的结果产生任何影响。
- 分布式事务就是要在分布式系统中的实现事务。
- 在分布式系统中,在保证可用性和不严重牺牲性能的前提下,光是要实现数据的一致性就已经非常困难了,所以出现了很多“残血版”的一致性,比如顺序一致性、最终一致性等等。
- 目前大家所说的分布式事务,更多情况下,是在分布式系统中事务的不完整实现。
- 在不同的应用场景中,有不同的实现,目的都是通过一些妥协来解决实际问题。
- 在实际应用中,比较常见的分布式事务实现有 2PC(Two-phase Commit,也叫二阶段提交)、TCC(Try-Confirm-Cancel) 和事务消息。
- 每⼀种实现都有其特定的使用场景,也有各自的问题,都不是完美的解决方案。
消息队列是如何实现分布式事务的?
- 事务消息需要消息队列提供相应的功能才能实现,Kafka 和 RocketMQ 都提供了事务相关功能。
- 以订单和购物车为例:
- 首先,订单系统在消息队列上开启⼀个事务。
- 然后订单系统给消息服务器发送一个“半消息”,这个半消息不是说消息内容不完整,它包含的内容就是完整的消息内容,半消息和普通消息的唯一区别是,在事务提交之前,对于消费者来说,这个消息是不可见的。
- 半消息发送成功后,订单系统就可以执行本地事务了,在订单库中创建一条订单记录,并提交订单库的数据库事务。
- 然后根据本地事务的执行结果决定提交或者回滚事务消息。
- 如果订单创建成功,那就提交事务消息,购物车系统就可以消费到这条消息继续后续的流程。
- 如果订单创建失败,那就回滚事务消息,购物车系统就不会收到这条消息。
- 这样就基本实现了“要么都成功,要么都失败”的一致性要求。
- 如果在第四步提交事务消息时失败了怎么办?
- 对于这个问题,Kafka 和 RocketMQ 给出了 2 种不同的解决方案。
- Kafka 的解决方案比较简单粗暴,直接抛出异常,让用户自行处理。
- 我们可以在业务代码中反复重试提交,直到提交成功,或者删除之前创建的订单进行补偿。
- RocketMQ 则给出了另外一种解决方案。
RocketMQ 中的分布式事务实现
- 在 RocketMQ 中的事务实现中,增加了事务反查的机制来解决事务消息提交失败的问题。
- 如果在提交或者回滚事务消息时发生网络异常,RocketMQ 的 Broker 没有收到提交或者回滚的请求,Broker 会定期去 Producer 上反查这个事务对应的本地事务的状态,然后根据反查结果决定提交或者回滚这个事务。