在RocketMQ4.3.0版本后,开放了事务消息这一特性,对于分布式事务而言,最常说的还是二阶段提交协议,那么RocketMQ的事务消息又是怎么一回事呢,这里主要带着以下几个问题来探究一下RocketMQ的事务消息:
事务消息是如何实现的
我们有哪些手段来监控事务消息的状态
事务消息的异常恢复机制
RocketMQ的事务消息是如何实现的
RocketMQ作为一款消息中间件,主要作用就是帮助各个系统进行业务解耦,以及对消息流量有削峰填谷的作用,而对于事务消息,主要是通过消息的异步处理,可以保证本地事务和消息发送同时成功执行或失败,从而保证数据的最终一致性,
事务消息从诞生到结束的整个时间线流程:
生产者发送消息到broker,该消息是prepare消息,且事务消息的发送是同步发送的方式。 broker接收到消息后,会将该消息进行转换,所有的事务消息统一写入Half Topic,该Topic默认是RMQ_SYS_TRANS_HALF_TOPIC ,写入成功后会给生产者返回成功状态。 本地生产获取到该消息的事务Id,进行本地事务处理。 本地事务执行成功提交Commit,失败则提交Rollback,超时提交或提交Unknow状态则会触发broker的事务回查。 若提交了Commit或Rollback状态,Broker则会将该消息写入到Op Topic,该Topic默认是RMQ_SYS_TRANS_OP_HALF_TOPIC,该Topic的作用主要记录已经Commit或Rollback的prepare消息,Broker利用Half Topic和Op Topic计算出需要回查的事务消息。如果是commit消息,broker还会将消息从Half取出来存储到真正的Topic里,从而消费者可以正常进行消费,如果是Rollback则不进行其他操作 如果本地事务执行超时或返回了Unknow状态,则broker会进行事务回查。若生产者执行本地事务超过6s则进行第一次事务回查,总共回查15次,后续回查间隔时间是60s,broker在每次回查时会将消息再在Half Topic写一次。回查次数和时间间隔都是可配置的。 执行事务回查时,生产者可以获取到事务Id,检查该事务在本地执行情况,返回状态同第一次执行本地事务一样。
从上述流程可以看到事务消息其实只是保证了生产者发送消息成功与本地执行事务的成功的一致性,消费者在消费事务消息时,broker处理事务消息的消费与普通消息是一样的,若消费不成功,则broker会重复投递该消息16次,若仍然不成功则需要人工介入。
事务消息的成功投递是需要经历三个Topic的
Half Topic:用于记录所有的prepare消息 Op Half Topic:记录已经提交了状态的prepare消息 Real Topic:事务消息真正的Topic,在Commit后会才会将消息写入该Topic,从而进行消息的投递
理解清楚事务消息在这三个Topic的流转就基本理解清楚了RocketMQ的事务消息的处理。接下来我们看看在源码中是如何使用这三个Topic的。
事务消息是如何处理回查的
在RocketMQ中,消息都是顺序写随机读的,以offset来记录消息的存储位置与消费位置,所以对于事务消息的prepare消息来说,不可能做到物理删除,broker启动时每间隔60s会开始检查一下有哪些prepare消息需要回查,从上面的分析我们知道,所有prepare消息都存储在Half Topic中,那么如何从该Topic中取出需要回查的消息进行回查呢?这就需要Op Half Topic以及一个内部的消费进度计算出需要回查的prepare消息进行回查:
Half Topic 默认Topic是RMQ_SYS_TRANS_HALF_TOPIC,建一个队列,存储所有的prepare消息
Op Half Topic默认是RMQ_SYS_TRANS_OP_HALF_TOPIC,建立的对列数与Half Topic相同,存储所有已经确定状态的prepare消息(rollback与commit状态),消息内容是该条消息在Half Topic的Offset
Half Topic消费进度,默认消费者是CID_RMQ_SYS_TRANS,每次取prepare消息判断回查时,从该消费进度开始依次获取消息。
Op Half Topic消费进度,默认消费者是CID_RMQ_SYS_TRANS,每次获取prepare消息都需要判断是否在Op Topic中已存在该消息了,若存在表示该prepare消息已结束流程,不需要再进行事务回查,每次判断都是从Op Topic中获取一定消息数量出来进行对比的,获取的消息就是从Op Topic中该消费进度开始获取的,最大一次获取32条。
获取Half Topic的所有队列,循环队列开始检测需要获取的prepare消息,实际上Half Topic只有一个队列。 获取Half Topic与Op Half Topic的消费进度。 调用fillOpRemoveMap方法,获取Op Half Topic中已完成的prepare事务消息。 从Half Topic中当前消费进度依次获取消息,与第3步获取的已结束的prepare消息进行对比,判断是否进行回查: 如果Op消息中包含该消息,则不进行回查, 如果不包含,获取Half Topic中的该消息,判断写入时间是否符合回查条件,若是新消息则不处理下次处理,并将消息重新写入Half Topic,判断回查次数是否小于15次,写入时间是否小于72h,如果不满足就丢弃消息,若满足则更新回查次数,并将消息重新写入Half Topic并进行事务回查, 在循环完后重新更新Half Topic与Op Half Topic中的消费进度,下次判断回查逻辑时,将从最新的消费进度获取信息。 生产客户端的ClientRemotingProcessor的processRequest方法会处理服务端的CHECK_TRANSACTION_STATE请求,最后会调用checkLocalTransactionState方法,该方法就是业务方可以自己实现事务消息回查逻辑的地方,并将结果最后用endTransactionOneway方法返回给Broker,该执行逻辑可以通过ClientRemotingProcessor的方法processRequest依次理解就可以了。
我们有哪些手段来监控事务消息的状态
事务消息主要有三个状态:
UNKNOW状态:表示事务消息未确定,可能是业务方执行本地事务逻辑时间耗时过长或者网络原因等引起的,该状态会导致broker对事务消息进行回查,默认回查总次数是15次,第一次回查间隔时间是6s,后续每次间隔60s, ROLLBACK状态,该状态表示该事务消息被回滚,因为本地事务逻辑执行失败导致 COMMIT状态,表示事务消息被提交,会被正确分发给消费者。
那么监控事务消息时,主要是查看该事务消息是否是处于我们想要的状态,而在事务消息生产者发送prepare消息成功后只能拿到一个transactionId,该id不是的RocketMQ消息存储的物理offset地址,RocketMQ只有在准备写入commitlog文件时才会生成真正的msgId,而这里可以获取的transactionId和msgId都是客户端生成的一个消息的唯一标识符,我们在这里称为uniqId,在broker端,会把该uniqId作为一个msgKey写入消息,所以可以通过该uniqId来查找uniqId的一些状态:
通过DefaultMQAdminExt的viewMessage(String topic, String msgId)方法可以消息的信息,这里topic参数是RMQ_SYS_TRANS_HALF_TOPIC ,该topic是真正的Half Topic,msgId传发送prepare消息获取的uniqId,这样可以获取prepare消息在Half Topic真正的offsetMsgId,
通过第一步获取的offsetMsgId继续调用viewMessage(String topic, String msgId)方法,但是topic是RMQ_SYS_TRANS_OP_HALF_TOPIC,这样可以获取Op Half Topic中该事务消息的状态,如果存在说明prepare消息已处理,否则可能仍在回查中或已被丢弃
如果在第二步查到了信息可以用uniqId和事务消息真正Topic继续调用viewMessage(String topic, String msgId)方法获取消息真正的信息,如果存在说明消息已被投递,否则该事务消息已被回滚。只通过Op Half Topic是不能确定消息状态的,这里的sysFlag被设置0,sysFlag是用于确定事务消息状态。
通过上述三步就可以确定事务消息的状态。
事务消息的异常恢复机制
事务消息的异常状态主要有:
生产者提交prepare消息到broker成功,但是当前生产者实例宕机了 生产者提交prepare消息到broker失败,可能是因为提交的broker已宕机 生产者提交prepare消息到broker成功,执行本地事务逻辑成功,但是broker宕机了未确定事务状态 生产提交prepare消息到broker成功,但是在进行事务回查的过程中broker宕机了,未确定事务状态
异常解决:
对于1:事务消息会根据producerGroup搜寻其他的生产者实例进行回查,所以transactionId务必保存在中央存储中,并且事务消息的pid不能跟其他消息的pid混用。 对于2:当前实例会搜寻其他的可用的broker-master进行提交,因为只有提交prepare消息后才会执行本地事务,所以没有影响,注意生产者报的是超时异常时,是不会进行重发的。 对于3:因为返回状态是oneway方式,此时如果消费者未收到消息,需要用手段确定该事务消息的状态,尽快将broker重启,broker重启后会通过回查完成事务消息。 对于4:同3,尽快重启broker。
---------------------
转自:https://blog.csdn.net/qq_28632173/article/details/83790243