开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天 点击查看活动详情
往期回顾
本期重点内容
本期我们恢复源码解析专栏的更新,更新的内容是关于RocketMQ事务消息的内容。
这里突出重点说一下,事务消息并不是为了解决分布式事务的,而解决的是本地事务与消息发送一致性的问题。 由于事务消息涉及的内容较多,我们分为上下两篇进行解析,上篇的内容主要是讲解客户端是如何发送事务消息的以及服务端是如何存储事务消息的。下篇的内容主要涉及到服务端是如何回查超时的事务消息以及已经完成的事务消息是如何进行“删除”的。
OK,让我们开始今天的内容。
解析事务消息实现原理上篇:客户端是如何发送事务消息以及服务端是如何存储事务消息的。
事务消息总览
预消息或者半消息指的就是用户要发送的业务消息,只不过在事务消息中就是这么称呼而已
以上梳理的就是RocketMQ事务消息处理的流程,总体来看分为三部分:
1、客户端发送预消息到服务端,此时消费端对此条预消息不可见。
2、客户端执行本地事务,并反馈事务的执行结果。
3、客户端根据本地事务的执行结果向服务端发起请求,告知预消息是提交还是回滚,也就是预消息是否消费方可见,可见就是可以进行消费。
事务消息客户端源码解析
按照上面事务消息总览的图示,我们深入到源码中,看一下在代码中是如何实现这一步步操作的。结合图示以及代码,相信读者能更好的理解事务消息的实现原理。
事务消息的生产者类为TransactionMQProducer,在使用这个类之前也是需要调用start方法进行初始化。
这边主要是构建了一个校验事务消息状态的线程池,在事务消息实现原理下篇中会用到,下一篇再进行解释。
调用 sendMessageInTransaction方法发送事务消息,Object类型的arg参数为用户自定义的业务参数
使用事务消息需要用户实现一个事务监听器接口,内部需要实现两个方法。
客户端执行事务消息的核心处理流程,在DefaultMQProducerImpl类的 sendMessageInTransaction方法实现。
这个方法的代码较多,篇幅较大,但是逻辑还是比较清晰的,可以主要拆分为三个部分:
1、发送预消息,也就是发送用户的业务消息,这边选用的发送方式为同步发送。
2、根据同步发送的结果,进行本地事务的处理。若发送成功,则回调用户实现监听器的executeLocalTransaction方法执行本地事务,若发送异常,则执行赋值本地事务执行状态为ROLLBACK_MESSAGE。
3、执行endTransaction方法,将本地事务执行的状态告知服务端,服务端根据不同的状态对预消息进行不同的处理。
执行endTransaction方法的逻辑如下。其中重要的一点就是根据 OffsetMsgId解码出消息在服务端存储的物理偏移量offset的值,服务端需要根据这个值去查找这条预消息。
事务消息服务端源码解析
在Broker端的初始化方法中会调用initialTransaction方法。这个方法是对事务消息相关组件的初始化。
客户端采用同步消息发送模式将预消息发送到服务端,该预消息会被打上一个事务消息的标签,在服务端处理器SendMessageProcessor处理时,会调用事务消息的API对其进行处理。
在 TransactionalMessageService处理事务消息的流程中,会将主题以及队列进行变更,写入到事务相关的主题以及队列中,并保留原来的主题以及队列,将变更后的消息按照原有流程写入commitlog中。 这种变更主题的逻辑操作,就使得客户端消费者不能马上看到该条消息,确保不会被消费。
EndTransactionProcessor处理器是服务端用于处理事务消息结束的流程。
EndTransactionProcessor处理器内部根据客户端本地事务执行的状态,进行不同的处理。
当客户端本地事务执行成功后,会将原来存储在事务消息主题中的消息重新流转至该条消息真正的业务主题上去,这种情况下,消费方就可以正常的消费到这条消息了。
当客户端本地事务执行异常,就不会将该条消息流转至真正的业务主题上去,这种情况下,消费方就不能消费到这条消息了。
不论客户端事务执行成功还是失败,都会执行一个叫deletePrepareMessage的方法,这个方法表示的内容就是去处理这些已经完成事务的预消息,虽然方法名称是delete开头的,但内部实现并没有去执行删除操作。
这一段逻辑执行下来,形成了如下的一个逻辑视图。至于形成的这个逻辑视图该如何使用,我们在下篇将会分析。
本期总结
本期为RocketMQ事务消息实现原理的上篇,主要介绍了RokcetMQ事务消息在客户端的处理流程以及服务端的写入流程。
在下篇中,我们将会讲解当预消息长时间没有后续处理时,服务端将会如何应对这种情况,会执行哪些操作。