RocketMQ的事务消息原理及源码解读

目录

 

1:实现思想

2:事务消息发送流程

3:消息提交,回滚

4:回查事务状态

5:总结


1:实现思想

RocketMQ事务消息的实现原理是基于两阶段提交(可以去了解一下XA)和定时事务状态回查来决定消息最终是提交还是回滚。一般地,应用程序在事务内完成相应的DB后,需要同步来调用mq相关的接口来发送消息,发送状态为prepare的消息(笔者称之为预消息),消息发送成功后,Rocketmq服务器会回调RocketMQ消息发送者的事件监听程序,记录消息的本地事务状态(相当于打一个标),该标与本地业务操作同属一个事务,确保消息发送与本地事务的原子性。

mq收到prepare的消息,会先备份消息的原主题和原消息消费队列(消息的载体),然后将消息存储为主题为 RMQ_SYS_TRANS_HALF_TOPIC(笔者称之为半主题)的消息消费队列中。

RocketMQ消息服务器开启一个定时任务,消费RMQ_SYS_TRANS_HALF_TOPIC消息队列的中消息,向消息发送端发起事务状态回查,应用程序根据保存的事务状态来回馈消息服务器事务的状态(commit,rollback,unkown),如果是提交或者是回滚,则服务提交或者回滚消息,如果是未知的消息,等待下一次回查,(为什么会存在未知的消息,一次轮询过去,而这个消息刚刚出来,还没有被消费),RocketMQ允许一条消息的回查间隔(轮询周期)与回查次数(超过多少次回滚),如果达到了n(最大回查次数)次,将会回滚消息

 

2:事务消息发送流程

1:入口很重要:发送事务消息的开始和普通的消息有点不同,事务消息的producer是TransactionMQProducer,如果看过笔者之前写过的话,应该知道是DefaultMQProducer,其实TransactionMQProducer是DefaultMQProducer的增强,在TransactionMQProducer的内部有一个transactionlistener,主要实现了本地事务状态执行,和本地事务事务回查两个接口

2:事务消息发送依赖的是TransactionMQProducer

  @Override
//通俗易懂,在事务中发送消息
    public TransactionSendResult sendMessageInTransaction(final Message msg,
        final Object arg) throws MQClientException {
//这里必须要依赖事务监听器,要是这个都没有话,相当于不可能存在事务状态回查和本地事务执行
        if (null == this.transactionListener) {
            throw new MQClientException("TransactionListener is null", null);
        }
//设置系统的半topic
        msg.setTopic(NamespaceUtil.wrapNamespace(this.getNamespace(), msg.getTopic()));
//最后还是调用的默认的mqProducer
        return this.defaultMQProducerImpl.sendMessageInTransaction(msg, null, arg);
    }

 

3:设置消息的主题,和生产者组

 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;
        //设置消息为事务消息,标记头
        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);
        }

 

4:执行本地事务

 LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
        Throwable localException = null;
        switch (sendResult.getSendStatus()) {
            case SEND_OK: {
                try {
                    if (sendResult.getTransactionId() != null) {
                        msg.putUserProperty("__transactionId__", sendResult.getTransactionId());
                    }
                    //从消息中获取事务id,为后续事务回查提供依据
                    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;
                }
            }

 

5:check消息,发送消息

 //是否设置了事务消息
                final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
                if (tranMsg != null && Boolean.parseBoolean(tranMsg)) {
                    //如果是事务消息的话,会给消息一个 PRE 的标志,后续会添加一个事务消息的专用处理器
                    sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
                }
 Map<String, String> oriProps = MessageDecoder.string2messageProperties(requestHeader.getProperties());
        //看看是不是事务消息
        String traFlag = oriProps.get(MessageConst.PROPERTY_TRANSACTION_PREPARED);
        if (traFlag != null && Boolean.parseBoolean(traFlag)) {
            if (this.brokerController.getBrokerConfig().isRejectTransactionMessage()) {
                response.setCode(ResponseCode.NO_PERMISSION);
                response.setRemark(
                    "the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1()
                        + "] sending transaction message is forbidden");
                return response;
            }
            putMessageResult = this.brokerController.getTransactionalMessageService().prepareMessage(msgInner);
        } else {
            putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);
        }

        return handlePutMessageResult(putMessageResult, response, request, msgInner, responseHeader, sendMessageContext, ctx, queueIdInt);

 

6:由于上述的消息的主题已经被改变成一个系统的主题,并且放入到对应的消息队列中,那么在消费的时候肯定是一个专门的线程去消费这个消息。

 

 

3:消息提交,回滚

1:在事务消息结束(没有真正的结束,只是一阶段的结束)事务消息的提交或者是回滚均由sendResult这个字段标识来确定,对应的处理器为org.apache.rocketmq.broker.processor.EndTransactionProcessor#processRequest,因为涉及到传输效率,在RocketMq中大量的使用Netty来做为io框架,所以涉及到数据的加解码(就是各种handler啦)

 @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.info("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;
        }

2:确定是否提交或者是回滚事务

 perationResult result = new OperationResult();
        //提交事务
        if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) {
            result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);
            if (result.getResponseCode() == ResponseCode.SUCCESS) {
                RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
                //返回成功
                if (res.getCode() == ResponseCode.SUCCESS) {
                    MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage());
                    msgInner.setSysFlag(MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), requestHeader.getCommitOrRollback()));
                    msgInner.setQueueOffset(requestHeader.getTranStateTableOffset());
                    //获得事务消息的偏移量,然后要将此消息放入到commitlog中进行文件的存储,最终是要存盘的
                    msgInner.setPreparedTransactionOffset(requestHeader.getCommitLogOffset());
                    //存储时间戳,一般用于消息的时间查找
                    msgInner.setStoreTimestamp(result.getPrepareMessage().getStoreTimestamp());
                    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;

 

4:回查事务状态

1:可以很绝对的认为,在RocketMQ中的事务消息只会存在两种状态(分别是 commit,rollback,)至于unkown的话只能做为一个中间的状态,也就是说,随着时间的推移,这种状态是一定会不存在的,要是真的不行,那就搬出来微积分--极限思想,世间万物最终归于平静,其实大数定律也是如此(扯得有点哲学的味道了,偏题了,偏题了)。

 

2:定时任务去扫描HALF_TOPIC,回查消息的事务状态

 protected void onWaitEnd() {
        //超时时间
        long timeout = brokerController.getBrokerConfig().getTransactionTimeOut();
        //最大的配置检测时间
        int checkMax = brokerController.getBrokerConfig().getTransactionCheckMax();
        //当前时间
        long begin = System.currentTimeMillis();
        log.info("Begin to check prepare message, begin time:{}", begin);
        this.brokerController.getTransactionalMessageService().check(timeout, checkMax, this.brokerController.getTransactionalMessageCheckListener());
        log.info("End to check prepare message, consumed time:{}", System.currentTimeMillis() - begin);
    }

3:拉取HALF_TOPIC主题下的所有消息队列,然后依次处理

 public void check(long transactionTimeout, int transactionCheckMax,
        AbstractTransactionalMessageCheckListener listener) {
        try {
            String topic = MixAll.RMQ_SYS_TRANS_HALF_TOPIC;
            Set<MessageQueue> msgQueues = transactionalMessageBridge.fetchMessageQueues(topic);
            if (msgQueues == null || msgQueues.size() == 0) {
                log.warn("The queue of topic is empty :" + topic);
                return;
            }
            log.debug("Check topic={}, queues={}", topic, msgQueues);
            for (MessageQueue messageQueue : msgQueues) {
                long startTime = System.currentTimeMillis();
                MessageQueue opQueue = getOpQueue(messageQueue);
                long halfOffset = transactionalMessageBridge.fetchConsumeOffset(messageQueue);
                long opOffset = transactionalMessageBridge.fetchConsumeOffset(opQueue);
                log.info("Before check, the queue={} msgOffset={} opOffset={}", messageQueue, halfOffset, opOffset);
                if (halfOffset < 0 || opOffset < 0) {
                    log.error("MessageQueue: {} illegal offset read: {}, op offset: {},skip this queue", messageQueue,
                        halfOffset, opOffset);
                    continue;
                }

4:事务回查实现

 @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;

5:总结

在RocketMQ中事务消息底层包装的是普通消息,在发送的时候更换其TOPIC,和对应的消息队列,然后有一个线程来消费这个消息队列上面的消息,分为两阶段提交,第一阶段是本地执行,第二阶段才是真正的提交,不过第二阶段的提交也是由条件的,而是通过事务状态回查的方式的来是实现的,最后会更新事务消息的ID,已达到将原来的消息更换成普通的消息进行发送,接着就会存入磁盘中持久化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值