在试图分析rocketMq如何保证未commit的事务消息不被消费的基础上我们要先搞清楚以下2个问题。
1.rocketMq事务消息如何实现?
2.rocketMq事务消息如何存储?
第一个问题在rocketMq官网有答案:
“它可以被认为是一个两阶段的提交消息实现,以确保分布式系统中的最终一致性。事务性消息保证了本地事务的执行和消息的发送能够以原子的方式进行。”
这是来着rocketMq官网的解答。
为了更形象的去描述事务消息的用处我们以实际业务来举例。
用户在我们这里借钱,借钱成功后我们要调用mq给用户发送借款成功短信,并且调用会员业务业务发送会员。
有一种情况是给用户会员发送失败了,但是MQ的短信已经发送成功了,用户就会投诉过来为什么我的会员没有到?这里只是举例一种场景,实际场景我们要通过其他手段来保证调用会员业务最终成功。
而rocketMq的事务性消息则通过两阶段提交来解决,如下图。
1.用户借款成功后首先发送half消息。
2.mq收到half消息后会返回ok状态。
3.此时可以执行会员发送逻辑。
4.会员发送如果成功则可以发起mq消息commit操作,失败则可以发起rollback操作。
如果会员发送一直没有相应怎么办?
所以实现mq事务消息还要有一个反查的接口,来查询本地操作是否成功。
本地事务执行结果查询总共有3个状态,COMMIT_MESSAGE 代表提交,ROLLBACK_MESSAGE 代表事务回滚,UNKNOW 代表未知继续查询。
与普通消息不同的是rocketMq事务消息会被赋值为事务消息的属性
1.那么事务消息被提交到那里去了?是否与普通消息一样存储?
rocketMq消息的存储如上图,生产者将消息发送给broker后,broker会采用同步或者异步的方式把消息顺序写入commit log,所有消息都在commit log中,当然写入前会加锁避免混乱。commitLog持久化后,会把里面的消息Dispatch到对应的Consume Queue上,当消费者进行消息消费时,会先读取consumerQueue , 逻辑消费队列ConsumeQueue保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量Offset,消息大小、和消息Tag的HashCode值。
2.事务消息是如何保存与普通消息如何区分的,如何保证不会被消费者消费到?
在源码中可以看到事务消息有一个内部处理方法在TransactionalMessageBridge.parseHalfMessageInner中,会将事务消息单独存放一个topic中,所以真正的消费者不会接收到这个未提交的halfMessage。
3.事务消息的提交与回滚
事务消息的提交与回滚在EndTransactionProcessor.processRequest方法中
事务的提交主要就是将原本消息的topic,queueId替换回来,不再使用half topic,当然回滚的情况下是不会替换的,而且事务的提交与回滚都会调用deletePrepareMessage方法。进入查看最终会构造一个op消息与本消息的映射,无论消息是提交还是回滚,消息都会被put到topic为RMQ_SYS_TRANS_OP_HALF_TOPIC的队列中,后面回查会用到。
RMQ_SYS_TRANS_HALF_TOPIC:prepare消息的主题,事务消息首先先进入到该主题。
RMQ_SYS_TRANS_OP_HALF_TOPIC:当消息服务器收到事务消息的提交或回滚请求后,会将消息存储在该主题下
4.定时回查处理
在TransactionalMessageService中有一个check方法,其中有很多check逻辑,时间、次数、是否有效等逻辑,这里不详细描述,最终符合回查的消息会启动一个线程对half topic的消息进行结果回查来决定是对half消息进行提交还是回滚。
将回查结果后的消息构造消息为OP消息,而OP消息与half消息大致相同只不过也是单独的topic,这样保证了不会被消费者消费到。OP消息代表该消息已经被提交或者回滚,所以记录一个OP操作。
当收到commit后则会恢复原本应该发送的topic信息和普通消息一样写入磁盘。在刚开始构造half消息的时候就已经把对应的topic信息和queueId以properties的形式进行封装了。
关注我的公众号 LearnMoreDoMore 可以及时看到更多技术分享文章。