RocketMQ源码(二十)之事务消息

版本

  1. 基于rocketmq-all-4.3.1版本

简介

  1. 事务消息流程

    • 应用程序事务发起者发送事务Prepare消息到MQ的broker端,发送成功后,Broker会回调事务监听器的本地事务执行方法执行本地事务
    • RocketMQ的broker收到Prepare消息后,先对消息的topic与消费队列进行备份,然后存储到主题为 RMQ_SYS_TRANS_HALF_TOPIC 的队列中(只有一个队列)
    • broker端启动时会启动一个定时任务,取出RMQ_SYS_TRANS_HALF_TOPIC中的消息向消息的发送者(生产组中的任意一个Producer)发起回查。发送端根据本地事务的具体执行状态返回提交/回滚/事务未知状态
    • 如果返回提交/回滚则broker对事务消息进行提交或者回滚
      • 如果返回了未知,则等待下次继续进行回查。达到回查最大次数依旧无法获取事务状态的消息,broker会对该事务消息做回滚操作。
      • 如果是提交事务,将事务消息恢复,写入到原始的Topic中,然后向RMQ_SYS_TRANS_OP_HALF_TOPIC的队列中写入一条消息。消息体就是当前这条事务消息的队列偏移值
      • 如果是回滚事务,只是向RMQ_SYS_TRANS_OP_HALF_TOPIC的队列中写入一条消息。消息体就是当前这条事务消息的队列偏移值
  2. 整体的交互过程
    在这里插入图片描述

  3. 为什么要采用额外的两个Topic来实现事务消息呢?

    • 为了让消费端不能消费Prepare消息,将Prepare消息先写入到RMQ_SYS_TRANS_HALF_TOPIC的队列中。这样的好处就是服务端处理发送事务Prepare消息的逻辑与普通消息的逻辑没什么区别,可以直接复用,只是额外做一下判断即可

    • RocketMQ所有的消息都是追加的方式写入到文件中,无论是提交或者回滚我们都不能直接修改或者删除原来的Prepare消息。这样会导致很多的脏页,严重影响性能。所以删除和修改就是向另一个RMQ_SYS_TRANS_HALF_TOPIC的队列里写入一条消息。这样当事务回查时,先从此队列中查询,如果找不到,说明是未知状态,此时需要再次回查

    • 以上这样做的缺点就是:所有消息都写入RMQ_SYS_TRANS_HALF_TOPIC队列,如果事务消息较多可能有瓶颈。并且消息会被存储多次

  4. 如果回查次数达到最大值或者文件已经过期,当前版本只是打印日志。如果Broker发现消息是未知状态,当再次处理时会重新追加这条消息

发送事务消息

  1. 发送事务消息使用TransactionMQProducer,此类继承DefaultMQProducer。委托DefaultMQProducerImpl执行发送逻辑

    @Override
    public TransactionSendResult sendMessageInTransaction(final Message msg,
        final Object arg) throws MQClientException {
         
        if (null == this.transactionListener) {
         
            throw new MQClientException("TransactionListener is null", null);
        }
    
        return this.defaultMQProducerImpl.sendMessageInTransaction(msg, null, arg);
    }
    
  2. DefaultMQProducerImpl#sendMessageInTransaction是发送的核心方法。主要逻辑

    • 校验事务监听器和消息相关配置(消息、topic、消息大小等)
    • 设置消息的事务属性,表示这是一个事务prepare消息。设置生产者组。用于回查本地事务,从生产者组中选择随机选择一个生产者。避免由于生产者挂掉导致一直回查失败
    • 发送prepare消息,返回成功结果后(SEND_OK)才执行本地回调事务监听器transactionListener。如果发送发生异常,则不会执行本地事务监听器
    • 发送本地处理结果给Broker,Broker根据状态回滚或者提交
    public TransactionSendResult sendMessageInTransaction(final Message msg,
                                                          final LocalTransactionExecuter localTransactionExecuter, final Object arg)
        throws MQClientException {
         
        // 校验是否配置事务监听器
        TransactionListener transactionListener = getCheckListener();
        if (null == localTransactionExecuter && null == transactionListener) {
         
            throw new MQClientException("tranExecutor is null", null);
        }
        Validators.checkMessage(msg, this.defaultMQProducer);
    
        SendResult sendResult = null;
        //设置属性,表示这是一个Prepare消息
        MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
      	//设置生产者组。用于回查本地事务,从生产者组中选择随机选择一个生产者。避免由于生产者挂掉导致一直回查失败
        MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
        try {
         
            sendResult = this.send(msg);
        } catch (Exception e) {
         
            throw new MQClientException("send message Exception", e);
        }
    
        LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
        Throwable localException = null;
        switch (sendResult.getSendStatus()) {
         
            case SEND_OK: {
         
                try {
         
                    // 事务ID
                    if (sendResult.getTransactionId() != null) {
         
                        msg.putUserProperty("__transactionId__", sendResult.getTransactionId());
                    }
                    //UNIQ_KEY,客户端发送时生成的唯一ID
                    String transactionId = msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
                    if (null != transactionId && !"".equals(transactionId)) {
         
                        msg.setTransactionId(transactionId);
                    }
                    // 执行本地配合的transactionListener逻辑。localTransactionExecuter已经过时
                    if (null != localTransactionExecuter) {
         
                        localTransactionState = localTransactionExecuter.executeLocalTransactionBranch(msg, arg);
                    } else if (transactionListener != null) {
         
                        log.debug("Used new transaction API");
                        //如果这个执行出现异常可能导致localTransactionState默认就是UNKNOW,如果返回null,则需要赋值一个默认值UNKNOW
                        localTransactionState = transactionListener.executeLocalTransaction(msg, arg);
                    }
                    if (null == localTransactionState) {
         
                        localTransactionState = LocalTransactionState.UNKNOW;
                    }
    
                    if (localTransactionState != LocalTransactionState.COMMIT_MESSAGE) {
         
                        log.info("executeLocalTransactionBranch return {}", localTransactionState);
                        log.info(msg.toString());
                    }
                } catch (Throwable e) {
         
                    log.info("executeLocalTransactionBranch exception", e);
                    log.info(msg.toString());
                    localException = e;
                }
            }
            break;
            // 未发送成功,设置回滚状态
            case FLUSH_DISK_TIMEOUT:
            case FLUSH_SLAVE_TIMEOUT:
            case SLAVE_NOT_AVAILABLE:
                localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
                break;
            default:
                break;
        }
    
        try {
         
            // 发送本地处理结果给Broker
            this.endTransaction(sendResult, localTransactionState, localException);
        } catch (Exception e) {
         
            log.warn("local transaction execute " + localTransactionState + ", but end broker transaction failed", e);
        }
    
        TransactionSendResult transactionSendResult = new TransactionSendResult();
        transactionSendResult.setSendStatus(sendResult.getSendStatus());
        transactionSendResult.setMessageQueue(sendResult.getMessageQueue());
        transactionSendResult.setMsgId(sendResult.getMsgId());
        transactionSendResult.setQueueOffset(sendResult.getQueueOffset());
        transactionSendResult.setTransactionId(sendResult.getTransactionId());
        transactionSendResult.setLocalTransactionState(localTransactionState);
        return transactionSendResult;
    }
    
    
  3. DefaultMQProducerImpl#endTransaction发送本地处理结果给Broker,本地处理结果会做转换

    • 如果localTransactionState==COMMIT_MESSAGE,设置为MessageSysFlag.TRANSACTION_COMMIT_TYPE( 0x2 << 2;//1000)
    • 如果localTransactionState==ROLLBACK_MESSAGE,设置为MessageSysFlag.TRANSACTION_ROLLBACK_TYPE(0x3 << 2;//1100)
    • 如果localTransactionState==UNKNOW,设置为MessageSysFlag.TRANSACTION_NOT_TYPE(0;//0000)
    public void endTransaction(
        final SendResult sendResult,
        final LocalTransactionState localTransactionState,
        final Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException {
         
        final MessageId id;
        // 服务端的消息ID
        if (sendResult.getOffsetMsgId() != null) {
         
            id = MessageDecoder.decodeMessageId(sendResult.getOffsetMsgId());
        } else {
         
            id = MessageDecoder.decodeMessageId(sendResult.getMsgId());
        }
        String transactionId = sendResult.getTransactionId();
        // prepare发送到哪个broker,就提交或者回滚在哪个Broker
        final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(sendResult.getMessageQueue().getBrokerName());
        EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
        requestHeader.setTransactionId(transactionId);
        //事务消息的提交偏移量
        requestHeader.setCommitLogOffse
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值