RocketMQ底层源码解析——事务消息的实现

RocketMQ 通信机制底层数据结构及源码解析 该篇文章主要介绍在 RocketMQ remoting 底层通信模块中的数据结构以及相关的一些源码的分析,对其客户端、服务端之间相互调用的协议、编解码进行了阐述,也说明了一下在 RocketMQ 中对于 TCP 流传输时会产生的粘包、拆包问题进行了描述以及 Netty 相关的解决 Case,最后对于 NettyRemotingServer Reactor 多线程模型设计进行了源码追踪以及模型图优化,希望对您有帮助,能够喜欢 阅读详情

1. 简介

RocketMQ自身实现了事务消息,可以通过这个机制来实现一些对数据一致性有强需求的场景,保证上下游数据的一致性。
在这里插入图片描述

以电商交易场景为例,用户支付订单这一核心操作的同时会涉及到下游物流发货、积分变更、购物车状态清空等多个子系统的变更。当前业务的处理分支包括:

  • 主分支订单系统状态更新:由未支付变更为支付成功。
  • 物流系统状态新增:新增待发货物流记录,创建订单物流记录。
  • 积分系统状态变更:变更用户积分,更新用户积分表。
  • 购物车系统状态变更:清空购物车,更新用户购物车记录。

引入RocketMQ之后保证上下游数据的一致性。
在这里插入图片描述
使用普通消息和订单事务无法保证一致的原因,本质上是由于普通消息无法像单机数据库事务一样,具备提交、回滚和统一协调的能力。 而基于 RocketMQ 的分布式事务消息功能,在普通消息基础上,支持二阶段的提交能力。将二阶段提交和本地事务绑定,实现全局提交结果的一致性。

事务消息发送分为两个阶段。第一阶段会发送一个半事务消息,半事务消息是指暂不能投递的消息,生产者已经成功地将消息发送到了 Broker,但是Broker 未收到生产者对该消息的二次确认,此时该消息被标记成“暂不能投递”状态,如果发送成功则执行本地事务,并根据本地事务执行成功与否,向 Broker 半事务消息状态(commit或者rollback),半事务消息只有 commit 状态才会真正向下游投递。如果由于网络闪断、生产者应用重启等原因,导致某条事务消息的二次确认丢失,Broker 端会通过扫描发现某条消息长期处于“半事务消息”时,需要主动向消息生产者询问该消息的最终状态(Commit或是Rollback)。这样最终保证了本地事务执行成功,下游就能收到消息,本地事务执行失败,下游就收不到消息。总而保证了上下游数据的一致性。

整个事务消息的详细交互流程如下图所示:
在这里插入图片描述
事务消息详细步骤:

  1. 生产者将半事务消息发送至 RocketMQ Broker。
  2. RocketMQ Broker 将消息持久化成功之后,向生产者返回 Ack 确认消息已经发送成功,此时消息暂不能投递,为半事务消息。
  3. 生产者开始执行本地事务逻辑。
  4. 生产者根据本地事务执行结果向服务端提交二次确认结果(Commit或是Rollback),服务端收到确认结果后处理逻辑如下:
    • 二次确认结果为Commit:服务端将半事务消息标记为可投递,并投递给消费者。
    • 二次确认结果为Rollback:服务端将回滚事务,不会将半事务消息投递给消费者。
  5. 在断网或者是生产者应用重启的特殊情况下,若服务端未收到发送者提交的二次确认结果,或服务端收到的二次确认结果为Unknown未知状态,经过固定时间后,服务端将对消息生产者即生产者集群中任一生产者实例发起消息回查。
  6. 需要注意的是,服务端仅仅会按照参数尝试指定次数,超过次数后事务会强制回滚,因此未决事务的回查时效性非常关键,需要按照业务的实际风险来设置。

2. 实战

分别启动:namesrv、broker、consumer、producer

producer示例代码:

public class TransactionProducer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        TransactionListener transactionListener = new TransactionListenerImpl();
        TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name");
        ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("client-transaction-msg-check-thread");
                return thread;
            }
        });
		producer.setNamesrvAddr("127.0.0.1:9876");
        producer.setExecutorService(executorService);
        producer.setTransactionListener(transactionListener);
        producer.start();

        String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"};
        for (int i = 0; i < 10; i++) {
            try {
                Message msg =
                    new Message("TopicTest", tags[i % tags.length], "KEY" + i,
                        ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
                SendResult sendResult = producer.sendMessageInTransaction(msg, null);
                System.out.printf("%s%n", sendResult);

                Thread.sleep(10);
            } catch (MQClientException | UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }

        for (int i = 0; i < 100000; i++) {
            Thread.sleep(1000);
        }
        producer.shutdown();
    }

    static class TransactionListenerImpl implements TransactionListener {
        private AtomicInteger transactionIndex = new AtomicInteger(0);

        private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<>();

        @Override
        public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            int value = transactionIndex.getAndIncrement();
            int status = value % 3;
            localTrans.put(msg.getTransactionId(), status);
            return LocalTransactionState.UNKNOW;
        }

        @Override
        public LocalTransactionState checkLocalTransaction(MessageExt msg) {
            Integer status = localTrans.get(msg.getTransactionId());
            if (null != status) {
                switch (status) {
                    case 0:
                        return LocalTransactionState.UNKNOW;
                    case 1:
                        return LocalTransactionState.COMMIT_MESSAGE;
                    case 2:
                        return LocalTransactionState.ROLLBACK_MESSAGE;
                    default:
                        return LocalTransactionState.COMMIT_MESSAGE;
                }
            }
            return LocalTransactionState.COMMIT_MESSAGE;
        }
    }
}

消费者示例代码:

public class Consumer {

    public static void main(String[] args) throws InterruptedException, MQClientException {

       
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");

        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.setNamesrvAddr("127.0.0.1:9876");

       
        consumer.subscribe("TopicTest1234", "*");

        consumer.registerMessageListener(new MessageListenerConcurrently() {

            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
   
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }
}

将producer和consumer的topic都设置为:TopicTest1234。然后再启动consumer和producer。可以看到TransactionProducer控制台输出日志:
在这里插入图片描述
Consumer控制台输出日志:
在这里插入图片描述

3. 原理分析

分别启动namesrv和broker服务,随后运行:org.apache.rocketmq.example.transaction.TransactionProducer.java,构建出一个TransactionListenerImpl对象之后,添加到TransactionProducer中,为半消息的发送以及本地事务校验做做准备。

核心入口:TransactionProducer#sendMessageInTransaction()

    @Override
    public TransactionSendResult sendMessageInTransaction(final Message msg,
        final Object arg) throws MQClientException {
        if (null == this.transactionListener) {
            throw new MQClientException("TransactionListener is null", null);
        }

        // 包装Topic,判断是否是重试Topic以及DLQ的Topic
        msg.setTopic(NamespaceUtil.wrapNamespace(this.getNamespace(), msg.getTopic()));
        // 发送事务消息
        return this.defaultMQProducerImpl.sendMessageInTransaction(msg, null, arg);
    }

DefaultMQProducerImpl#sendMessageInTransaction()

    public TransactionSendResult sendMessageInTransaction(final Message msg,
        final LocalTransactionExecuter localTransactionExecuter, final Object arg)
        throws MQClientException {
        // 获取事务监听器,就程序开头的时候传入的TransactionListenerImpl对象
        TransactionListener transactionListener = getCheckListener();
        // 新版本推荐事务监听器,已不使用localTransactionExecuter
        if (null == localTransactionExecuter && null == transactionListener) {
            throw new MQClientException("tranExecutor is null", null);
        }

        // 设置DelayTimeLevel 参数
        if (msg.getDelayTimeLevel() != 0) {
            MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_DELAY_TIME_LEVEL);
        }

        // 消息校验
        Validators.checkMessage(msg, this.defaultMQProducer);

        SendResult sendResult = null;
        // 设置属性:TRAN_MSG为true
        MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
        // 设置属性:PGROUP为producerGroup
        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) {
                        // 设置事务id
                        msg.putUserProperty("__transactionId__", sendResult.getTransactionId());
                    }
                    String transactionId = msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
                    if (null != transactionId && !"".equals(transactionId)) {
                        msg.setTransactionId(transactionId);
                    }
                    if (null != localTransactionExecuter) {
                    	// 执行本地事务检查
                        localTransactionState = localTransactionExecuter.executeLocalTransactionBranch(msg, arg);
                    } else if (transactionListener != null) {
                        log.debug("Used new transaction API");
                    	// ② 执行本地事务检查                        
                        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 {
        	// ③ 调用endTransaction判断是否结束事务
            this.endTransaction(msg, 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;
    }

在该方法中核心步骤分别位于①②③标识处。

①:调用链路为最终抵达:DefaultMQProducerImpl#sendDefaultImpl(),也就是producer发消息核心方法。
在sendDefaultImpl()中有一处比较关键的代码:

				// 事务
                final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
                if (Boolean.parseBoolean(tranMsg)) {
                    sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
                }

此处代码给这条消息标识上事务消息的标志,最后通过mQClientAPIImpl往broker发送一条RPC的请求。

Broker端接受到请求之后经过请求code的分发,这条请求将由SendMessageProcessor#asyncSendMessage进行处理。

    private CompletableFuture<RemotingCommand> asyncSendMessage(ChannelHandlerContext ctx, RemotingCommand request,
                                                                SendMessageContext mqtraceContext,
                                                                SendMessageRequestHeader requestHeader) {
		// 相关代码省略...
		
        // 事务消息标识,从请求头中获取
        String transFlag = origProps.get(MessageConst.PROPERTY_TRANSACTION_PREPARED);
        if (Boolean.parseBoolean(transFlag)) {
            if (this.brokerController.getBrokerConfig().isRejectTransactionMessage()) {
                response.setCode(ResponseCode.NO_PERMISSION);
                response.setRemark(
                        "the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1()
                                + "] sending transaction message is forbidden");
                return CompletableFuture.completedFuture(response);
            }
            // 事务消息处理
            putMessageResult = this.brokerController.getTransactionalMessageService().asyncPrepareMessage(msgInner);
        } else {
            // 将消息内容存储到CommitLog文件
            putMessageResult = this.brokerController.getMessageStore().asyncPutMessage(msgInner);
        }
        return handlePutMessageResultFuture(putMessageResult, response, request, msgInner, responseHeader, mqtraceContext, ctx, queueIdInt);
    }

asyncSendMessage方法处会从消息属性中获取到transFlag标识,判断到这条消息是一条半消息,然后调用this.brokerController.getTransactionalMessageService().asyncPrepareMessage(msgInner)。

方法调用链最终来到TrasactionalMessageBridge#asyncPutHalfMessage(),

    public CompletableFuture<PutMessageResult> asyncPutHalfMessage(MessageExtBrokerInner messageInner) {
        return store.asyncPutMessage(parseHalfMessageInner(messageInner));
    }
   /**
     * 消息事务半消息
     * @param msgInner
     * @return
     */
    private MessageExtBrokerInner parseHalfMessageInner(MessageExtBrokerInner msgInner) {
        // 设置真正要发送的Topic
        MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC, msgInner.getTopic());
        // 设置真正要发送的queueId
        MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID,
            String.valueOf(msgInner.getQueueId()));
        msgInner.setSysFlag(
            MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), MessageSysFlag.TRANSACTION_NOT_TYPE));
        // 设置半消息Topic
        msgInner.setTopic(TransactionalMessageUtil.buildHalfTopic());
        // 设置半消息queueId
        msgInner.setQueueId(0);
        msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
        return msgInner;
    }

parseHalfMessageInner方法做了两件事,将这条半消息真正要发送的Topic以及queueId另外存起来,然后将这条半消息要发送到指定存储半消息的的RMQ_SYS_TRANS_HALF_TOPIC中,然后将queueId设置为0。(存储半消息的Topic只有1个queueId)
最后将这条半消息存入到CommitLog中,则步骤①执行完成。

② 步骤①发送半消息到CommitLog中存储之后,此时消息发送结果为SEND_OK,接着将调用TransactionListener#executeLocalTransaction()方法,检查本地事务的状态,此处由开发者自行实现代码逻辑。

③ 检查本地事务结束之后,会调用endTransaction()方法来尝试结束此次的事务消息。

public void endTransaction(
        final Message msg,
        final SendResult sendResult,
        final LocalTransactionState localTransactionState,
        final Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException {
        final MessageId id;
        if (sendResult.getOffsetMsgId() != null) {
            id = MessageDecoder.decodeMessageId(sendResult.getOffsetMsgId());
        } else {
            id = MessageDecoder.decodeMessageId(sendResult.getMsgId());
        }
        // 获取消息事务id
        String transactionId = sendResult.getTransactionId();
        // 获取broker地址
        final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(sendResult.getMessageQueue().getBrokerName());
        EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
        requestHeader.setTransactionId(transactionId);
        // 设置消息comimtlog偏移量
        requestHeader.setCommitLogOffset(id.getOffset());
        switch (localTransactionState) {
            case COMMIT_MESSAGE:	// 提交消息
                requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_COMMIT_TYPE);
                break;
            case ROLLBACK_MESSAGE:	// 回滚消息
                requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE);
                break;
            case UNKNOW:	// 未知状态
                requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_NOT_TYPE);
                break;
            default:
                break;
        }

        doExecuteEndTransactionHook(msg, sendResult.getMsgId(), brokerAddr, localTransactionState, false);
        requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
        requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());
        requestHeader.setMsgId(sendResult.getMsgId());
        String remark = localException != null ? ("executeLocalTransactionBranch exception: " + localException.toString()) : null;
        // 往broker发送endTransactionOneway请求
        this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark,
            this.defaultMQProducer.getSendMsgTimeout());
    }

broker端EndTransactionProcessor#processRequest接收到endTransaction请求:

@Override
    public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws
        RemotingCommandException {
        final RemotingCommand response = RemotingCommand.createResponseCommand(null);
        final EndTransactionRequestHeader requestHeader =
            (EndTransactionRequestHeader)request.decodeCommandCustomHeader(EndTransactionRequestHeader.class);
        LOGGER.debug("Transaction request:{}", requestHeader);
        if (BrokerRole.SLAVE == brokerController.getMessageStoreConfig().getBrokerRole()) {
            response.setCode(ResponseCode.SLAVE_NOT_AVAILABLE);
            LOGGER.warn("Message store is slave mode, so end transaction is forbidden. ");
            return response;
        }
		// 判断是事务check类型
        if (requestHeader.getFromTransactionCheck()) {
            switch (requestHeader.getCommitOrRollback()) {
                case MessageSysFlag.TRANSACTION_NOT_TYPE: {
                    LOGGER.warn("Check producer[{}] transaction state, but it's pending status."
                            + "RequestHeader: {} Remark: {}",
                        RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                        requestHeader.toString(),
                        request.getRemark());
                    return null;
                }

                case MessageSysFlag.TRANSACTION_COMMIT_TYPE: {
                    LOGGER.warn("Check producer[{}] transaction state, the producer commit the message."
                            + "RequestHeader: {} Remark: {}",
                        RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                        requestHeader.toString(),
                        request.getRemark());

                    break;
                }

                case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: {
                    LOGGER.warn("Check producer[{}] transaction state, the producer rollback the message."
                            + "RequestHeader: {} Remark: {}",
                        RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                        requestHeader.toString(),
                        request.getRemark());
                    break;
                }
                default:
                    return null;
            }
        } else {
            switch (requestHeader.getCommitOrRollback()) {
                case MessageSysFlag.TRANSACTION_NOT_TYPE: {
                    LOGGER.warn("The producer[{}] end transaction in sending message,  and it's pending status."
                            + "RequestHeader: {} Remark: {}",
                        RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                        requestHeader.toString(),
                        request.getRemark());
                    return null;
                }

                case MessageSysFlag.TRANSACTION_COMMIT_TYPE: {
                    break;
                }

                case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: {
                    LOGGER.warn("The producer[{}] end transaction in sending message, rollback the message."
                            + "RequestHeader: {} Remark: {}",
                        RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                        requestHeader.toString(),
                        request.getRemark());
                    break;
                }
                default:
                    return null;
            }
        }
        OperationResult result = new OperationResult();
        if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) {
            // 这里提交requestHeader,实际是从commitlog中获取halfMessage
            result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);
            if (result.getResponseCode() == ResponseCode.SUCCESS) {
                // 检查halfMessage状态
                RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
                if (res.getCode() == ResponseCode.SUCCESS) {
                	// ④ 半消息处理成功之后,调用endMessageTransaction()发起事务消息结束
                    MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage());
                    msgInner.setSysFlag(MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), requestHeader.getCommitOrRollback()));
                    msgInner.setQueueOffset(requestHeader.getTranStateTableOffset());
                    msgInner.setPreparedTransactionOffset(requestHeader.getCommitLogOffset());
                    msgInner.setStoreTimestamp(result.getPrepareMessage().getStoreTimestamp());
                    // 清楚事务标识
                    MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED);
                    // ⑤ 发送真正的完整的消息
                    RemotingCommand sendResult = sendFinalMessage(msgInner);
                    if (sendResult.getCode() == ResponseCode.SUCCESS) {
                    	// ⑦ 删除半消息
                        this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
                    }
                    return sendResult;
                }
                return res;
            }
        } else if (MessageSysFlag.TRANSACTION_ROLLBACK_TYPE == requestHeader.getCommitOrRollback()) {
            // ⑥ 发起消息回滚,获取要回滚的半消息
            result = this.brokerController.getTransactionalMessageService().rollbackMessage(requestHeader);
            if (result.getResponseCode() == ResponseCode.SUCCESS) {
                // 查询半消息
                RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
                if (res.getCode() == ResponseCode.SUCCESS) {
                    // ⑦  删除半消息
                    this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
                }
                return res;
            }
        }
        response.setCode(result.getResponseCode());
        response.setRemark(result.getResponseRemark());
        return response;
    }

④ 调用endMessageTransaction

private MessageExtBrokerInner endMessageTransaction(MessageExt msgExt) {
        MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
        // 取出真正要发送是Topic
        msgInner.setTopic(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC));
        // 取出真正要发送的queueid
        msgInner.setQueueId(Integer.parseInt(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_QUEUE_ID)));
        msgInner.setBody(msgExt.getBody());
        msgInner.setFlag(msgExt.getFlag());
        msgInner.setBornTimestamp(msgExt.getBornTimestamp());
        msgInner.setBornHost(msgExt.getBornHost());
        msgInner.setStoreHost(msgExt.getStoreHost());
        msgInner.setReconsumeTimes(msgExt.getReconsumeTimes());
        msgInner.setWaitStoreMsgOK(false);
        msgInner.setTransactionId(msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX));
        msgInner.setSysFlag(msgExt.getSysFlag());
        TopicFilterType topicFilterType =
            (msgInner.getSysFlag() & MessageSysFlag.MULTI_TAGS_FLAG) == MessageSysFlag.MULTI_TAGS_FLAG ? TopicFilterType.MULTI_TAG
                : TopicFilterType.SINGLE_TAG;
        long tagsCodeValue = MessageExtBrokerInner.tagsString2tagsCode(topicFilterType, msgInner.getTags());
        msgInner.setTagsCode(tagsCodeValue);
        MessageAccessor.setProperties(msgInner, msgExt.getProperties());
        msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgExt.getProperties()));
        MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC);
        MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID);
        return msgInner;
    }

该方法将这条半消息设置成真正发送的Topic以及queueId。

⑤ 调用EndTransactionProcessor#sendFinalMessage(),发送真正的消息。最终调用MessageStore#putMessage()将该条消息存入CommitLog中。

⑥ 调用TransactionalMessageServiceImpl#rollbackMessage()对半消息进行回滚,但该方法实则是读取到半消息,然后再检查这条半消息。

⑦ 发送真正的消息或者是回滚半消息成功之后,随后调用TransactionalMessageServiceImpl#deletePrepareMessage()删除半消息。

经过①~⑦的步骤,整个RocketMQ的事务消息流程也就结束了,但是这里有一个关键点还没有讲解,当半消息发送成功了,本地事务执行成功发送本地事务状态时发生了broker断电或者是本地事务状态没有发送成功时,该如何保证整个流程能够正常运行呢?答案就是broker端会在启动时启动一个定时任务区检查本地事务的状态,也就是方法:TransactionListener#checkLocalTransaction()。

也就是Broker端会向消息生产者发起事务回查,第一次回查后仍未获取到事务状态,则之后每隔一段时间会再次回查。此外,需要注意的是事务消息的生产组名称 ProducerGroupName不能随意设置。事务消息有回查机制,回查时Broker端如果发现原始生产者已经崩溃,则会联系同一生产者组的其他生产者实例回查本地事务执行情况以Commit或Rollback半事务消息。

参考

深度解析:基于 RocketMQ 实现分布式事务的技术实践与原理探究 half消息即半消息,和普通消息的区别是该消息不会立马被消费者消费,原因是half消息的存在是为了保证生产者本地事务和消费者的原子性和一致性,其过程如上文所介绍,初始发送的half消息是存储在MQ一个内存队列中(并未投递到topic中),只有生产者本地事务成功并发送commit通知后,这个消息才会被持久化到commitLog同时提交到topic队列中,此时消费者才能够消费该消息并执行本地事务。 阅读详情

相关推荐

RocketMQ的事务消息是如何实现的?

那如果用事务消息,先提交一个半消息,然后执行本地事务,在发送一个commit的半消息。4.回滚事务消息:如果本地事务执行失败,应用程序通知RocketMQ Broker 回滚该事务,RocketMQ将该消息的状态从‘prepared’改为‘rollback’,并将该消息从事务消息日志中删除,从而保证该消息不会被消费者消费。在发送事务消息时,首先向RocketMQ Broker 发送一条‘half消息’(半消息),半消息将被存储在broker端的事务消息日志中,但是这个消息还不能被消费者消费。

zhangjiaming_zjm的博客 893

RocketMQ事务消息

RocketMQ 也允许我们像mysql 一样发送具有事务特征的消息。MQ 的消息补偿过程(当本地代码执行失败时):事务消息仅与生产者有关,与消费者无关。MQ 的事务流程(本地代码正常执行)

一个双子座的Java攻城狮 350

RocketMQ DefaultMQProducer#send 方法源码解析:生产者投递消息(二)

该篇博文主要分析几种不同方式的投递消息方式采用的核心方法,SYNC 采用的是 MQClientAPIImpl#sendMessageSync 方法处理、ASYNC 采用的是 MQClientAPIImpl#sendMessageAsync、ONEWAY 采用的是 NettyRemotingClient#invokeOneway 方法处理,而 ONEWAY、ASYNC 采用了信号量的方式去获取令牌凭证去向 Broker 发起请求,同时,由于是异步必须是要有回调存在的,所以会有一个异步调度线程去阻塞等待消息的投

vnjohn 博主 > 主线分享后端领域业务设计知识、源码思想、架构设计 1922

一文详解RocketMQ的事务消息原理

事务消息不支持延时消息和批量消息。为了避免单个消息被检查太多次而导致半队列消息累积,MQ默认将单个消息的检查次数限制为 15 次,用户可以通过参数来修改此限制。如果已经回查某条消息超过次的话,则Broker将丢弃此消息,并打印错误日志。事务消息将在参数指定的时间长度之后被回查。当发送事务消息时,用户还可以通过设置用户属性来改变这个限制,该参数优先于参数。事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。

2301_76607156的博客 2372

Spring Cloud整合RocketMQ 5.x全解析:事务消息、流转原理与实战代码

在分布式系统与微服务架构中,消息队列是解耦、异步、流量削峰的核心组件。‌RocketMQ凭借其高吞吐、事务消息、顺序消息等特性,成为企业级应用的首选‌,而Spring Cloud的整合能力进一步简化了消息驱动架构的实现。然而,许多开发者仅停留在基础API调用,对事务消息的底层流转、补偿机制及生产级配置缺乏深度理解。‌本文以Spring Cloud与RocketMQ 5.x最新版本为基准,通过源码级解析与完整示例,揭示事务消息的完整生命周期、Broker与本地事务的协同逻辑,并给出高可用实践方案。

weixin_42593797的博客 1176

深入剖析RocketMQ源码:消息传递的奥秘

消息传递的奥秘涉及的核心代码包括消息队列的持久化、消息发送、消息消费等。消息发送和消息消费则涉及到发送器和消费者的实现。在消费消息的过程中,RocketMQ还提供了消息重试、顺序消费、批量消费、并发消费等丰富的特性,可以为开发者提供更为灵活和方便的开发方式。在RocketMQ中,消息以消息队列(MQ)的形式存储,消费者从消息队列中获取消息。总的来说,RocketMQ的消息传递奥秘涉及到RocketMQ的核心组件和特性,对于了解RocketMQ的底层实现原理和开发者们来说,是必不可少的。

程序员Linc 1330

源码深入篇:RocketMQ 核心源码精读指南

这与 Kafka 按分区存储的设计不同——Kafka 在 Topic 和分区数量增长时,写入性能会下降,而 RocketMQ 的表现则稳定得多。💡 阅读建议:如果你第一次读 RocketMQ 源码,建议按这个顺序入手:remoting(通信基础)→ namesrv(路由)→ store(存储)→ broker(服务端)→ client(客户端)。由浅入深,逐步推进。从入门认知到架构原理,从存储机制到发送消费,从进阶特性到部署运维,再到今天的源码深入——你已经完整走过了 RocketMQ 学习的全过程。

vda16wCB5的博客 221

爱快路由(ikuai)多WAN口配置实战:提升网络带宽与稳定性

本文详细解析了爱快路由(ikuai)多WAN口配置的实战技巧,包括双线智能分流、毫秒级故障切换和带宽叠加利用等核心功能。通过七种配置方案和智能QoS策略,帮助企业提升网络带宽与稳定性,实现99.9%以上的高可用性。

weixin_30566063的博客 698

RocketMQ底层原理和源码解析

本文的内容主要包括:MQ的对比和选型、RocketMQ原理、RocketMQ源码解析、事务消息。 如发现本文中所写有错误或者不准确之处,非常欢迎指出,我会及时调研修正! ps:转载的同学请标出原文链接,尊重别人的劳动成果,谢谢

时无英雄、使竖子成名 2万+

Vivado时序约束实战:input_delay在高速接口设计中的关键作用

本文深入探讨了在Vivado时序约束中,input_delay对于高速接口设计的关键作用。通过一个RGMII以太网接口调试的实际案例,解释了为何仅靠仿真正确而实际数据采集会失败,并揭示了set_input_delay约束的本质是告知时序分析工具外部芯片的时序关系,而非添加物理延迟。文章详细讲解了如何根据数据手册计算约束值、针对不同采样时钟方案(如移相时钟)进行约束,并指导如何通过时序报告验证约束效果,是解决FPGA高速接口时序问题的实战指南。

ee345的博客 252

嵌入式音频架构 - AudioWeaver 整体概念

AudioWeaver 是DSP Concept公司推出的基于嵌入式平台的音频框架。由AWE Core embedded processing engine 和 Audio Weaver Designer IDE 组成。 Audio Weaver Designer 有三个版本,ST Edition, Standard Edtion 和 Pro Edtion。 Audio Weaver Designer界面: Audio Weaver 的整体使用架构: AWD算法流文件,AWS配置脚本文件,AWB嵌入式b

甲客实验室 6868

RocketMQ事务消息实战

这份文档从构建一个键值数据库的关键架构入手,不仅带你建立起全局观,还帮你迅速抓住核心主线。除此之外,还会具体讲解数据结构、线程模型、网络框架、持久化、主从同步和切片集群等,帮你搞懂底层原理。相信这对于所有层次的Redis使用者都是一份非常完美的教程了。你的支持,我的动力;祝各位前程似锦,offer不断!!!《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!你的支持,我的动力;祝各位前程似锦,offer不断!!!

2401_83703951的博客 939

GD32单片机开发环境搭建(Keil5安装)

1.安装MDK5 由于GD32 Keil 版本例程的工程是基于Keil MDK-ARM 5.25 uVision5 创建的,所以我在此也安装MDK5.25版本。 下载链接:https://pan.baidu.com/s/1E4tcDQvsVl0xezU3pEyJTA 提取码:s1fr 其他版本下载链接:https://cloud.tencent.com/developer/article/1605195 选择安装路径(不要装到中文路径下,避免不必要的麻烦): 用户信息随便...

fangye945a的博客 3万+

rocketmq事务消息源码解析

事务消息是apache rockerMq提供的一种高级消息类型,支持在分布式场景下保障消息生产和本地事务的最终一致性。

sinat_35803474的博客 236

还在用 RaiDrive/Rclone 挂载网盘?教您使用Windows 原生 WebDAV 丝滑直连 AList/Openlist,让你的网盘秒变本地硬盘!

网盘文件分散管理?😩 别再依赖第三方工具了!本文教你如何在 Windows 下,利用原生 WebDAV 功能,将 Openlist 网盘直接映射为本地硬盘!💻 操作简单,告别繁琐,让你的云端文件像本地文件一样丝滑!🚀 轻松实现高效管理,快来体验吧!

XiaoqiangClub的博客 2971

Python 爬虫实战:抓取新闻网站上的新闻标题和内容

随着信息化的进展,新闻网站已经成为人们获取新闻和时事资讯的主要途径。许多新闻网站提供了丰富的内容,涵盖了社会、政治、科技、娱乐等多个领域。通过抓取新闻网站的内容,我们可以分析新闻的趋势,获取某一领域的最新动态,甚至为数据分析和机器学习提供数据支持。本文将详细介绍如何使用 Python 编写爬虫,抓取新闻网站上的新闻标题和内容。requests:发送 HTTP 请求,获取页面内容。:解析 HTML 页面并提取数据。Selenium:抓取动态加载的网页内容。新闻API。

2201_76125261的博客 3407

详述RocketMQ实现分布式事务原理+案例

一、经典的分布式事务场景 示例:假设A给B转账500块,并且它俩处于不同的微服务中。 要求:A减500块、B加上500块。 可能发生的情况: 1)A减500(成功),B加500(成功) 2)A减500(失败),B加500(失败) 3)A减500(成功),B加500(失败) 4)A减500(失败),B加500(成功) 上述1)、2)两种情况是可以保证事务的一致性的。而3)、4)两种情况是无法保证事务的一致性的。 二、RocketMQ实现分布式事务原理 RocketMQ在4.3版本开始提供了分布式事务功

健身变秃,coding变强 1万+

PHP实现获取本机ip及mac地址​(附完整源码)

PHP实现获取本机ip及mac地址​(附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 350

TDMQ RocketMQ 版事务消息原理解析

在分布式架构系统中,确保跨服务应用的数据一致性始终是系统设计的核心挑战之一。TDMQ RocketMQ 版作为一款基于 Apache RocketMQ 构建的企业级消息中间件,凭借其高可用性和高可靠性特点,通过提供完善的事务消息机制,为这一难题提供了专业的解决方案。本文将结合核心源码,深入解析 RocketMQ 事务消息的实现原理,希望能帮助开发者去构建更健壮的分布式事务系统。

qq_36668144的博客 940

大数据学习指南从入门到精通

利用框架的力量,看懂游戏规则,才是入行的前提大多数人不懂,不会,不做,才是你的机会,你得行动,不能畏首畏尾选择才是拉差距关键,风向,比你流的汗水重要一万倍,逆风划船要累死人的为什么选择学习大数据开发,不选择Java开发?借棋弈做比喻,智商高的不要选择五子琪,要选择围棋,它能长久地吸引你。不都是在棋盘上一颗黑子一颗白子地下吗?因为围棋更复杂,能够掌握如此复杂的技艺、产生稳定输出的棋手、让我们更佩服。选择学习大数据开发也如此,能让你的职业生涯走得更远,少走弯路。

Lansonli(蓝深李)的博客 2万+

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

MQ - RocketMQ底层原理和源码解析

本文的内容主要包括:MQ的对比和选型、RocketMQ原理、RocketMQ源码解析、事务消息。 Rocketmq性能压测客户端:https://github.com/Force-King/rocketmq-test 一、MQ的对比和选型 目前业界主要的消息中间件有:ActiveMQ、RabbitMQ、ZeroMQ、Kafka、MetaMQ、RocketMQ。其中开源的,社区比较活跃的是ActiveMQ、RabbitMQ、Kafka、RocketMQ。所以,我们做技术选型时,需要考虑从以上4种开源的M

迦南的专栏 1092

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

RocketMQ分布式事务消息原理解析

RocketMQ事务消息实现原理 文章目录RocketMQ事务消息实现原理事务消息RocketMQ事务消息流程概要1.事务消息发送及提交2.补偿流程RocketMQ事务消息设计1.事务消息在一阶段对用户不可见2.Commit和Rollback操作以及Op消息的引入3.Op消息的存储和对应关系4.Half消息的索引构建5.如何处理二阶段失败的消息?RocketMQ事务消息阅读指引 事务消息 Apa...

鲸鱼的博客 1052

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

RocketMQTemplate 实现消息发送

springboot整合RocketMQ,发送普通消息、过滤消息、同步消息、延时消息、批量消息、异步消息、单向消息、顺序消息、事务消息样例并分析源码。

HGW的博客 7101

RocketMQ常见问题与优化建议:原理、源码、实践全解析

RocketMQ以其高性能、可扩展、强可靠等特性,成为企业级消息中间件首选。生产、存储、消费每一环节都蕴含着巧妙的设计与高阶算法。应对常见问题,需理解其底层原理,结合业务场景进行针对性优化。集成主流技术栈和深度调优,可大幅提升系统稳定性与吞吐能力。知其然,更知其所以然——RocketMQ生产实践,核心在于理解与调优。如需进一步了解具体源码细节、分布式事务实战或大数据集成案例,欢迎留言交流!

IT技术学习与工作笔记分享 1778
上一篇: 项目开发中代码风格以及规范总结(持续更新...)
下一篇: MacPro M1 搭建kafka2.8.0源码环境
CoderBruis
博客等级 码龄10年 4444粉丝 102原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值