首先要明白,事务消息是基于两阶段提交来完成的
概述
-
第一阶段:也就是发送 Message,但这里的 Message 叫 Half Message,即半事务消息。此类型的 Message 是不会被 Consumer 消费到的。
-
第二阶段:如果半事务消息投递成功,则会开始执行本地事务。这里分为如下三种情况。
-
如果本地事务执行成功,则会向 Broker 发送
commit
消息,被commit
过后的 Message 才能被 Consumer 消费到。 -
如果本地事务执行失败,则会向 Broker 发送
rollback
消息,Broker 则会将刚刚投递的半事务消息删除,从而保证上下游数据的一致性。 -
如果 Producer 实例或者网络出现了问题,Producer 没能及时地将本地事务执行的结果通知 Broker,Broker 会通过扫描发现某条 Message 长时间处于“半事务消息”状态,Broker 会变被动为主动,主动地向 Producer 询问此 Message 对应的事务状态。
-
这就是事务消息的大致原理。
详情
Producer操作部分
1 检查TransactionListener等组件是否被初始化
2 清除消息的延迟属性(事务不支持延迟)
3 将消息标记为Half Message(半事务消息)
Producer 会向 Message 的 Property 当中写入属性
4 将事务消息投递到Broker
Broker部分
1 检查是否为事务消息,与普通消息分流
2 储存事务消息,并备份原Topic与Messagequeue
类似延迟消息,事务消息专门储存在RMQ_SYS_TRANS_HALF_TOPIC的Topic中,不过它只有一个队列,都储存在0号队列中。(一个 Topic 下如果只有 1 个 MessageQueue,那么这个 Topic 下的所有 Message 就是全局有序的,它们会按照先来后到的顺序被消费)
3 Producer执行本地事务,将结果发给Broker,Producer 会向 Broker 发送一个 RequestCode 为 END_TRANSACTION 的请求,来通知 Broker 执行相关操作。
4 Broker根据事务执行结果判断
- commit,根据offset(偏移量,用来定位消息)取出Half Message,
构建新的Message,还原其Topic与 Messagequeue,重新投递,并再次持久化储存。
还会将原先的Half Message打上删除tags(标签)
- Rollback,删除Half Message
- UnKnow,进行事务回查
- 删除的都会扔入RMQ_SYS_TRANS_OP_HALF_TOPIC队列
5 事务回查
Broker在启动时创建线程一分钟扫描一次事务消息队列中是否需要回查,有两个关键参数:
- timeout,超过这个时间后才会进行回查,6秒
- checkMax,最大回查几次,超过就删除
6 回查过程
- 取出Half Message
- 进行各种校验,是否被删,是否达到最大次数,是否达到最大存活时间,默认3天
以上任何校验不通过就会被丢弃,
扔入TRANS_CHECK_MAX_TIME_TOPIC队列
- 将当前 Message 再次投递进事务消息专用队列当中,为什么呢?
这是因为,一旦判定为需要执行事务回查逻辑,那么当前这条 Half Message 就算已经被消费了。但如果后续调用 resolveHalfMsg() 失败了没有拿到结果怎么办?这里明显是还要重试的,所以为了让整个链路串起来,在没达到最大的校验次数之前,都还需要将其投递到事务队列当中,以便下次再次执行 Check 逻辑。
- Broker异步向Produce查询事务状态
Broker 会向 Producer 发送一个 RequestCode 为 CHECK_TRANSACTION_STATE 的请求
- Producer 拿到事务的结果之后,会向 Broker 发送 RequestCode 为 END_TRANSACTION 的请求,串起来了
附带一张很不错的流程图
正常消费:
异常消费:(事务失败)