引言
前面我们已经简单地介绍了 RocketMQ 的整体设计思路,本文着重其中事务消息部分的实现细节。更多相关文章和其他文章均收录于贝贝猫的文章目录。
事务消息
Apache RocketMQ 在4.3.0版中已经支持分布式事务消息,这里 RocketMQ 采用了 2PC 的思想来实现了提交事务消息,同时增加一个补偿逻辑来处理二阶段超时或者失败的消息,如下图所示。
上图说明了事务消息的大致方案,其中分为两个流程:正常事务消息的发送及提交、事务消息的补偿流程。
事务消息发送及提交:
- 发送消息(half 消息)
- 服务端响应消息写入结果
- 根据发送结果执行本地事务(如果写入失败,此时 half 消息对业务不可见,本地事务逻辑不执行)
- 根据本地事务状态执行 Commit或者 Rollback(Commit 操作生成消息索引,此后消息才对消费者可见)
如果上述第四步没有执行成功,RocketMQ 会有自己的补偿过程:
- 对没有 Commit/Rollback 的事务消息(pending 状态的消息),从服务端发起一次“回查”
- Producer 收到回查消息,检查回查消息对应的本地事务的状态
- 根据本地事务状态,重新 Commit 或者 Rollback
补偿阶段用于解决消息 Commit 或者 Rollback 发生超时或者失败的情况。
Half 消息
在 RocketMQ 事务消息的主要流程中,一阶段的消息如何对用户不可见。其中,事务消息相对普通消息最大的特点就是一阶段发送的消息对用户是不可见的。那么,如何做到写入消息但是对用户不可见呢?RocketMQ事务消息的做法是:如果消息是 Half 消息,将备份原消息的主题与消息消费队列,然后改变主题为 RMQ_SYS_TRANS_HALF_TOPIC。由于消费组未订阅该主题,故消费端无法消费 Half 类型的消息,然后 RocketMQ 会开启一个定时任务,从 Topic 为 RMQ_SYS_TRANS_HALF_TOPIC 中拉取消息进行消费,根据生产者组获取一个服务提供者发送回查事务状态请求,根据事务状态来决定是提交或回滚消息。
其实改变消息主题是 RocketMQ 的常用“套路”,回想一下延时消息和重试消息的实现机制。
Op 消息
在完成一阶段写入一条对用户不可见的消息后,二阶段如果是 Commit 操作,则需要让消息对用户可见;如果是 Rollback 则需要撤销一阶段的消息。先说 Rollback 的情况。对于 Rollback,本身一阶段的消息对用户是不可见的,其实不需要真正撤销消息(实际上 RocketMQ 也无法去真正的删除一条消息,因为是顺序写文件的)。但是区别于这条消息没有确定状态(Pending 状态,事务悬而未决),需要一个操作来标识这条消息的最终状态。RocketMQ事务消息方案中引入了 Op 消息的概念,用 Op 消息标识事务消息已经确定的状态(Commit 或者 Rollback)。如果一条事务消息没有对应的 Op 消息,说明这个事务的状态还无法确定(可能是二阶段失败了)。引入 Op 消息后,事务消息无论是 Commit 或者 Rollback 都会记录一个 Op 操作。Commit 相对于 Rollback 只是在写入 Op 消息前创建 Half 消息的索引。
RocketMQ 将 Op 消息写入到全局一个特定的 Topic 中,这个 Topic 是一个内部的 Topic(像 Half 消息的 Topic 一样),不会被用户消费。Op 消息的内容为对应的 Half 消息的存储的 Offset,这样通过 Op 消息能索引到 Half 消息进行后续的回查操作。
Half消息的索引构建
在执行二阶段 Commit 操作时,需要构建出Half消息的索引。一阶段的 Half 消息由于是写到一个特殊的 Topic,所以二阶段构建索引时需要读取出 Half 消息,并将 Topic 和 Queue 替换成真正的目标的 Topic 和 Queue,之后通过一次普通消息的写入操作来生成一条对用户可见的消息。所以 RocketMQ 事务消息二阶段其实是利用了一阶段存储的消息的内容,在二阶段时恢复出一条完整的普通消息,然后走一遍消息写入流程。
处理二阶段失败
如果在 RocketMQ 事务消息的二阶段过程中失败了,例如在做 Commit 操作时,出现网络问题导致 Commit 失败,那么需要通过一定的策略使这条消息最终被 Commit。RocketMQ 采用了一种补偿机制,称为“回查”。Broker 端对未确定状态的消息发起回查,将消息发送到对应的 Producer 端(同一个 Group 的 Producer),由 Producer 根据消息来检查本地事务的状态,进而执行 Commit 或者 Rollback。Broker 端通过对比 Half 消息和 Op 消息进行事务消息的回查并且推进 CheckPoint(记录那些事务消息的状态是确定的)。
值得注意的是,RocketMQ 并不会无休止的的信息事务状态回查,默认回查 15 次,如果15次回查还是无法得知事务状态,RocketMQ 默认回滚该消息。
参考内容
[1]《RocketMQ技术内幕》
[2]《RocketMQ实战与原理解析》
[3] 老生常谈——利用消息队列处理分布式事务
[4] RocketMQ架构解析
[5] MappedByteBuffer VS FileChannel 孰强孰弱?
[6] 文件 IO 操作的一些最佳实践
[7] 海量数据处理之Bloom Filter详解
[8] rocketmq GitHub Wiki