• RocketMQ源码理解之事务消息


    二提交阶段

    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中,以便可以向前推进消费进度,最后发送回查请求
    再根据事务的状态发送提交/回滚请求

  • 相关阅读:
    MongoDB 搭建可复制群集
    jquery获取json对象中的key小技巧,遍历json串所有key,value
    21-spring学习-springMVC实现CRUD
    java线程--volatile实现可见性
    java线程-synchronized实现可见性代码
    java线程-java多线程之可见性
    java反射--通过反射了解集合泛型的本质
    java反射--方法反射的基本操作
    java反射--获取成员变量信息
    java反射-获取方法信息
  • 原文地址:https://www.cnblogs.com/fyusac/p/16105118.html
Copyright © 2020-2023  润新知