二提交阶段
RocketMQ的事务消息总体也可认为是采用二提交阶段:
- 在事务开始的时候,先发送一个
事务消息
RocketMQ使用
事务消息发送
构建half 消息
Broker事务消息提交/回滚流程
后台有个EndTransactionProcessor 线程用来处理事务提交/回滚消息,该线程会在BrokerController#registerProcessor方法中进行注册,在NettyRemotingAbstract#processRequestCommand中根据业务请求类型获取对应的Processor 处理器
public void registerProcessor(){
...
/**
* EndTransactionProcessor
*/
this.remotingServer.registerProcessor(RequestCode.END_TRANSACTION, new EndTransactionProcessor(this), this.endTransactionExecutor);
this.fastRemotingServer.registerProcessor(RequestCode.END_TRANSACTION, new EndTransactionProcessor(this), this.endTransactionExecutor);
...
}
public void processRequestCommand(...) {
// 根据业务请求码获取对应的处理器Processor
NettyRequestProcessor processor = pair.getObject1();
RemotingCommand response = processor.processRequest(ctx, cmd);
callback.callback(response);
}
实际调用的则是EndTransactionProcessor#processRequest 方法来处理事务的提交/回滚消息
- (1)和(6)都是根据commitLogOffset从commitlog文件中查找获取half消息,返回OperationResult实例
- (2)和(7)都是验证消息必要字段:验证消息的commitlog与请求消息的commitlog是否一致、验证消息的队列偏移量与请求信息的队列偏移量是否一致、验证消息的commitlog与请求消息的commitlog是否一致等
- (3)将Half消息恢复原消息,恢复事务消息真实的主题、队列,并设置事务ID,
- (4)将消息写入真正的主题队列,会调用
DefaultMessageStore#putMessage
将消息存储在commitlog文件中,此时的消息会被转发到原消息主题对应的消费队列,被消费者消费 - (5) 写入成功后,在该消息打上'd'标签,表示该消息已经被处理,但实际上并没有删除,后面仍会持久化存储消息,(8)是同样的操作
事务消息的提交和回滚核心实现就是根据commitlogOffset找到消息,
(1)如果是提交动作,就恢复原消息的主题与队列,再次存入commitlog文件进而转到消息消费队列,供消费者消费,
(2)回滚消息与提交事务消息不同的是,提交事务消息会将消息恢复原主题与队列,而回滚消息则不会
两种情况最终都会将原预处理消息存入一个新的主题RMQ_SYS_TRANS_OP_HALF_TOPIC
,打上'd'标签,代表该消息已被处理,该消息也最终会被存储在commitlog文件中。
Broker 定时回查
Broker接收了half消息,如果在s时间内接收不到commit或者Rollback命令,60s为周期定时执行回查操作,对应的后台服务就是TransactionalMessageCheckService,这个服务会定时扫描half队列,去请求反查接口看看事务是否执行成功,具体执行的方法是TransactionalMessageServiceImpl#check
方法
TransactionalMessageCheckService#run
=> ServiceThread#waitForRunning
=> TransactionalMessageCheckService#onWaitEnd
=> TransactionalMessageServiceImpl#check
方法源码虽然比较多,但实际上就是获取所有的half消息,调用TransactionalMessageServiceImpl#fillOpRemoveMap
对已经处理的消息进行去重,避免重复发送回查请求,最后调用异步线程发送事务回查请求,事务回查检测本地事务后,根据事务状态重新发送提交/回滚请求
- 获取所有的half消息,即
RMQ_SYS_TRANS_HALF_TOPIC
为主题的所有队列,然后取出这个队列对应的half_op主题下的队列,即RMQ_SYS_TRANS_OP_HALF_TOPIC
主题下的队列 - 调用fillOpRemoveMap对已经处理的消息去重
- 调用 putBackHalfMsgQueue将half消息再次写入commitlog中,然后再发送事务回查请求;将Half消息再次写入Half消息的队列中是为了能够向前推送消费进度
RMQ_SYS_TRANS_HALF_TOPIC:处于该主题下的消息表示属于Half消息,事务消息首先进入到该主题下(Half消息)
RMQ_SYS_TRANS_OP_HALF_TOPIC:则表示该消息已被处理,无论是commit还是Rollback, 当broker 收到事务消息的提交/回滚请求后,将消息存储到该主题下(op消息)
事务消息执行流程:
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendMessageInTransaction
- Producer首先发送一个Half消息给broker,等待事务响应
- 当Product接收到成功发送Half消息的通知后,下一步则开始执行本地事务
- Product执行本地事务,根据本地事务执行的状态,向Broker发送Commit提交/Rollback回滚请求,Broker接收到Commit请求后,将消息推送给Consumer;如果是Rollback请求,就不投递,保存三天后删除(只是被标记为删除)
- Product等待Broker发送的消息确认(等待多久会认为超时?)
- 如果因为网络等原因导致Product没有等到消息确认就查询本地事务所处的状态
- 根据本地事务所处状态再次向Broker发送Commit/Rollback 消息
Half消息是暂时不能被消费的消息,Product将其成功发送到Broker上,但暂时不能被消费,被标记为暂不投递状态,处于这种状态下的消息被称为Half消息
画一张RocketMQ的事务流程图
** 事务消息总的执行流程:**
Producer 发送事务消息,Broker将其转成Half消息,备份topic和queueid ,
Producer 执行本地事务,根据本地事务执行状态,发送提交或回滚请求,Broker接收到提交请求,先将Half消息恢复成原消息的topic和queueid ,放到可以供消费者消费的队列,并将其标记为删除,如果是回滚则直接标记为删除,两种情况下都再将消息写入half_op队列,打上'd'标签,表示该消息被处理过,以供后面进行消息回查
Producer在60内没有收到请求回复,进行消息回查,查找Half 消息中对应的op消息进行去重,然后将消息再保存到commitlog中,以便可以向前推进消费进度,最后发送回查请求
再根据事务的状态发送提交/回滚请求