事务消息实现思想
RocketMQ事务消息的实现原理基于两阶段提交和定时事务状态回查来决定消息最终是提交还是回滚。
1)应用程序在事务内完成相关业务数据落库后,需要同步调用RocketMQ消息发送接口,发送状态为prepare的消息。消息发送成功后,RocketMQ服务器会回调RocketMQ消息发送者的事件监听程序,记录消息的本地事务状态,该相关标记与本地业务操作同属一个事务,确保消息发送与本地事务的原子性。
2)RocketMQ在收到类型为prepare的消息时,会首先备份消息的原主题与原消息消费队列,然后将消息存储在主题为RMQ_SYS_TRANS_HALF_TOPIC的消息消费队列中。
3)RocketMQ消息服务器开启一个定时任务,消费RMQ_SYS_TRANS_HALF_TOPIC的消息,向消息发送端(应用程序)发起消息事务状态回查,应用程序根据保存的事务状态回馈消息服务器事务的状态(提交、回滚、未知),如果是提交或回滚,则消息服务器提交或回滚消息,如果是未知,待下一次回查,RocketMQ允许设置一条消息的回查间隔与回查次数,如果在超过回查次数后依然无法获知消息的事务状态,则默认回滚消息。
事务消息发送流程
RocketMQ事务消息发送者为org.apache.rocketmq.client.producer.TransactionMQProducer。
1、TransactionMQProducer
1 ) TransactionListener transactionListener:事务监听器,主要定义实现本地事务状态执行、本地事务状态回查两个接口 。
2 ) ExecutorService executorService:事务状态回查异步执行线程池。
2、TransactionListener
1 ) LocalTransactionState executeLocalTransaction (final Message msg, final Object arg ):执行本地事务 。
2 ) LocalTransactionState checkLocalTransaction (final MessageExt msg):事务消息状态回查 。
Step1:首先为消息添加属性,TRAN_MSG 和 PGROUP,分别表示消息为 prepare 消息、消息所属消息生产者组。设置消息生产者组的目的是在查询事务消息本地事务状态时,从该生产者组中随机选择一个消息生产者即可,然后通过同步调用方式向RocketMQ发送消息。
Step2:根据消息发送结果执行相应的操作 。
1 )如果消息发送成功,则执行 TransactionListener#executeLocalTransaction 方法,该方法的职责是记录事务消息的本地事务状态, 例如可以通过将消息唯一 ID 存储在数据中,并且该方法与业务代码处于同一个事务,与业务事务要么一起成功,要么一起失败。这里是事务消息设计的关键理念之一,为后续的事务状态回查提供唯一依据。
2 )如果消息发送失败,则设置本次事务状态为 LocalTransactionState.ROLLBACK_MESSAGE 。
Step3:结束事务 。 根据第二步返回的事务状态执行提交、回滚或暂时不处理事务。
1)LocalTransactionState.COMMIT_MESSAGE: 提交事务 。
2)LocalTransactionState.ROLLBACK_MESSAGE:回滚事务 。
3)LocalTransactionState.UNKNOW:结束事务 ,但不做任何处理。
在使用事务消息TransactionListener#execute方法除了记录事务消息状态后,应该返回LocalTransactionState.UNKNOW,事务消息的提交与回滚通过事务消息状态回查时再决定是否提交或回滚。
事务消息与非事务消息发送流程的主要区别 :
如果是事务消息则备份消息的原主题与原消息消费队列,然后将主题变更为 RMQ_SYS_TRANS_HALF TOPIC,消费队列变更为0,然后消息按照普通消息存储在 commitlog 文件进而转发到 RMQ SYS_TRANS_HALF_TOPIC 主题对应的消息消费队列。也就是说,事务消息在未提交之前并不会存入消息原有主题,自然也不会被消费者消费。既然变更了主题,RocketMQ通常会采用定时任务(单独的线程 )去消费该主题,然后将该消息在满足特定条件下恢复消息主题,进而被消费者消费。它与 RocketMQ 定时消息的处理过程如出一辙。
事务消息回查事务状态
事务消息存储在消息服务器时主题被替换为RMQ_SYS_TRANS_HALF_TOPIC,执行完本地事务返回本地事务状态为 UN_KNOW 时,结束事务时将不做任何处理,而是通过事务状态定时回查以期得到发送端明确的事务操作(提交事务或回滚事务)。
RocketMQ 通过 TransactionalMessageCheckService 线程定时去检测 RMQ_SYS_TRANS_HALF_TOPIC 主题中的消息,回查消息的事务状态。TransactionalMessageCheckService 的检测频率默认为 1 分钟,可通过在 broker.conf文件中设置 transactionChecklnterval 来改变默认值,单位为毫秒。
transactionTimeOut:事务的过期时间,只有当消息的存储时间加上过期时间大于系统当前时间时,才对消息执行事务状态回查 ,否则在下一次周期中执行事务回查操作。
transactionCheckMax:事务回查最大检测次数,如果超过最大检测次数还是无法获知消息的事务状态,RocketMQ 将不会继续对消息进行事务状态回查,而是直接丢弃即相当于回滚事务 。
事务消息的处理涉及如下两个主题。
RMQ_SYS_TRANS_HALF_TOPIC:prepare 消息的主题,事务消息首先进入到该主题。
RMQ_SYS_TRANS_OP_HALF_TOPIC:当消息服务器收到事务消息的提交或回滚请求后,会将消息存储在该主题下。
首先构建事务状态回查请求消息,核心参数包含消息offsetld、消息 ID (索引)、消息事务 ID、事务消息队列中的偏移量、消息主题、消息队列。然后根据消息的生产者组,从中随机选择一个消息发送者。最后向消息发送者发送事务回查命令。
事务回查命令的最终处理者为 ClientRemotingProssor 的 processRequest 方法,最终将任务提交到TransactionMQProducer的线程池中执行,最终调用应用程序实现的TransactionListener 的 checkLocalTransaction 方法 ,返回事务状态。如果事务状态为 LocalTransactionState#COMMIT_MESSAGE,则向消息服务器发送提交事务消息命令;如果事务状态为 Loca!TransactionState#ROLLBACK MESSAGE,则向 Broker 服务器发送回滚事务操作;如果事务状态为 UNOWN,则服务端会忽略此次提交 。
提交或回滚事务
本节继续探讨两阶段提交的第二个阶段:提交或回滚事务 。
根据消息所属的消息队列获取 Broker 的 IP与端口 信息,然后发送结束事务命令,其关键就是根据本地执行事务的状态分别发送提交、回滚或“不作为”的命令。Broker服务端的结束事务处理器为:EndTransactionProcessor。
如果结束事务动作为提交事务,则执行提交事务逻辑,其关键实现如下 。
1 )首先从结束事务请求命令中获取消息的物理偏移量(commitlogOffset)。
2 )然后恢复消息的主题、消费队列,构建新的消息对象。
3 )然后将消息再次存储在 commitlog 文件中,此时的消息主题则为业务方发送的消息,将被转发到对应的消息消费队列,供消息消费者消费。
4 )消息存储后,删除 prepare 消息,其实现方法并不是真正的删除,而是将 prepare消息存储到 RMQ_SYS_TRANS_OP_HALF_TOPIC 主题中,表示该事务消息(prepare 状态的消息)已经处理过(提交或回滚),为未处理的事务进行事务回查提供查找依据。
事务的回滚与提交的唯一差别是无须将消息恢复原主题,直接删除 prepare 消息即可,同样是将预处理消息存储在 RMQ_SYS_TRANS_OP_HALF_TOPIC 主题中,表示已处理过该消息。
总结
RocketMQ 事务消息基于两阶段提交和事务状态回查机制来实现,所谓的两阶段提交,即首先发送 prepare 消息,待事务提交或回滚时发送 commit、rollback 命令。 再结合定时任务,RocketMQ 使用专门的线程以特定的频率对 RocketMQ 服务器上的 prepare 信息进行处理,向发送端查询事务消息的状态来决定是否提交或回滚消息 。
发送事务消息可能存在3种情况:
1)正常情况:步骤1 -> 步骤2 -> 步骤3 -> 步骤4
发送方先发送prepare消息,发送成功后执行本地事务,本地事务的执行成功通知RocketMQ服务器进行commit投递消息,本地事务执行失败通知RocketMQ服务器rollback删除消息不进行投递。
2)回查情况:步骤1 -> 步骤2 -> 步骤3 -> 步骤5 -> 步骤6 -> 步骤7
发送方发送prepare消息,发送成功后执行本地事务,但由于本地事务可能执行时间较长,如果超过一定时间RocketMQ服务器没有收到处理结果,不知道是commit还是rollback,这时会根据定时任务进行发送方本地事务状态的回查,在监听器中进行事务状态回查,如果查到本地事务处理结果,则返回commit或rollback状态;如果还是未查到本地事务处理结果,返回unknow状态,RocketMQ服务器不会做操作仍会进行定时回查。
监听器(在发送方中定义)需要实现TransactionListener,有两个功能:prepare消息发送成功,则在executeLocalTransaction()中执行本地事务;在checkLocalTransaction()中执行本地事务状态回查逻辑。
3)异常情况:步骤1 -> 步骤2 -> 步骤3 -> 步骤5 -> 步骤6 -> 人工干预队列
基于情况2的情况,如果回查次数达到上限,将消息放入人工干预队列。