rocketmq事务消息发送原理解析

今天来说一下rocketmq的事务消息

什么是事务消息 (官网:rocketmq.apache.org/docs/transa…

它可以被认为是两阶段提交消息的实现,以确保分布式系统中的最终一致性。事务性消息确保本地事务的执行和消息的发送可以原子地执行。

事务消息

/**
 * 简单发送事务消息的案例
 *
 * @author leon
 * @date 2020-09-17 15:52:22
 */
public class TransactionProducer {

    private static final int CORE_THREAD = Runtime.getRuntime().availableProcessors() + 1;

    @SuppressWarnings("all")
    public static void main(String[] args) throws Exception {
        TransactionMQProducer mqProducer = new TransactionMQProducer("transaction-producer");
        mqProducer.setNamesrvAddr("localhost:9876");
        //自定义线程池
        ExecutorService executorService = Executors.newFixedThreadPool(CORE_THREAD);
        mqProducer.setExecutorService(executorService);
        mqProducer.setTransactionListener(new CustomTransactionListener());
        mqProducer.start();

        try {
            for (int i = 0; i < 10; i++) {

                Message message = new Message();
                message.setTopic("transaction-message");
                message.setTags("tagA");
                message.setBody(("transaction message2:" + i).getBytes());
                TransactionSendResult transactionSendResult = mqProducer.sendMessageInTransaction(message, "key&" + i);

                TimeUnit.SECONDS.sleep(1);
            }
        } finally {
            mqProducer.shutdown();
        }
    }
}
复制代码

根据发送的逻辑简单找了一下 没有找到对消息的处理 生成half消息处理以及OP消息 不如换个思路 因为之前half消息的topic为RMQ_SYS_TRANS_HALF_TOPIC 我们找一下使用他的地方

在这个位置 org.apache.rocketmq.broker.transaction.queue.TransactionalMessageUtil#buildHalfTopic 构造half消息

public static String buildHalfTopic() {
    return MixAll.RMQ_SYS_TRANS_HALF_TOPIC;
}
复制代码

在继续向上找

  • org.apache.rocketmq.broker.transaction.queue.TransactionalMessageBridge#parseHalfMessageInner
  • org.apache.rocketmq.broker.transaction.queue.TransactionalMessageBridge#getHalfMessage
  • org.apache.rocketmq.broker.processor.AdminBrokerProcessor#toMessageExtBrokerInner

第二个为获取 half消息后续调用为

/**
 * Read half message from Half Topic
 *
 * @param mq     Target message queue, in this method, it means the half message queue.
 * @param offset Offset in the message queue.
 * @param nums   Pull message number.
 * @return Messages pulled from half message queue.
 */
private PullResult pullHalfMsg(MessageQueue mq, long offset, int nums) {
    return transactionalMessageBridge.getHalfMessage(mq.getQueueId(), offset, nums);
}
复制代码

由于是获取 我们需要看的是创建 在进行找第一位置的调用 org.apache.rocketmq.broker.transaction.queue.TransactionalMessageBridge#parseHalfMessageInner

private MessageExtBrokerInner parseHalfMessageInner(MessageExtBrokerInner msgInner) {
    MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC, msgInner.getTopic());
    MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID,
            String.valueOf(msgInner.getQueueId()));
    msgInner.setSysFlag(
            MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), MessageSysFlag.TRANSACTION_NOT_TYPE));
    msgInner.setTopic(TransactionalMessageUtil.buildHalfTopic());
    msgInner.setQueueId(0);
    msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
    return msgInner;
}

//org.apache.rocketmq.broker.transaction.queue.TransactionalMessageBridge#putHalfMessage
public PutMessageResult putHalfMessage(MessageExtBrokerInner messageInner) {
    return store.putMessage(parseHalfMessageInner(messageInner));
}

//org.apache.rocketmq.broker.transaction.queue.TransactionalMessageServiceImpl#prepareMessage
@Override
public PutMessageResult prepareMessage(MessageExtBrokerInner messageInner) {
    return transactionalMessageBridge.putHalfMessage(messageInner);
}

//org.apache.rocketmq.broker.processor.SendMessageProcessor#sendMessage
private RemotingCommand sendMessage(final ChannelHandlerContext ctx,
                                    final RemotingCommand request,
                                    final SendMessageContext sendMessageContext,
                                    final SendMessageRequestHeader requestHeader) throws RemotingCommandException {

    final RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class);
    final SendMessageResponseHeader responseHeader = (SendMessageResponseHeader) response.readCustomHeader();

    response.setOpaque(request.getOpaque());

    response.addExtField(MessageConst.PROPERTY_MSG_REGION, this.brokerController.getBrokerConfig().getRegionId());
    response.addExtField(MessageConst.PROPERTY_TRACE_SWITCH, String.valueOf(this.brokerController.getBrokerConfig().isTraceOn()));

    log.debug("receive SendMessage request command, {}", request);

    final long startTimstamp = this.brokerController.getBrokerConfig().getStartAcceptSendRequestTimeStamp();
    if (this.brokerController.getMessageStore().now() < startTimstamp) {
        response.setCode(ResponseCode.SYSTEM_ERROR);
        response.setRemark(String.format("broker unable to service, until %s", UtilAll.timeMillisToHumanString2(startTimstamp)));
        return response;
    }

    response.setCode(-1);
    super.msgCheck(ctx, requestHeader, response);
    if (response.getCode() != -1) {
        return response;
    }

    final byte[] body = request.getBody();

    int queueIdInt = requestHeader.getQueueId();
    TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());

    if (queueIdInt < 0) {
        queueIdInt = Math.abs(this.random.nextInt() % 99999999) % topicConfig.getWriteQueueNums();
    }
    //构建 MessageExtBrokerInner 参数 
    MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
    msgInner.setTopic(requestHeader.getTopic());
    msgInner.setQueueId(queueIdInt);

    if (!handleRetryAndDLQ(requestHeader, response, request, msgInner, topicConfig)) {
        return response;
    }

    msgInner.setBody(body);
    msgInner.setFlag(requestHeader.getFlag());
    MessageAccessor.setProperties(msgInner, MessageDecoder.string2messageProperties(requestHeader.getProperties()));
    msgInner.setBornTimestamp(requestHeader.getBornTimestamp());
    msgInner.setBornHost(ctx.channel().remoteAddress());
    msgInner.setStoreHost(this.getStoreHost());
    msgInner.setReconsumeTimes(requestHeader.getReconsumeTimes() == null ? 0 : requestHeader.getReconsumeTimes());
    String clusterName = this.brokerController.getBrokerConfig().getBrokerClusterName();
    MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_CLUSTER, clusterName);
    msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
    PutMessageResult putMessageResult = null;
    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;
        }
        //将消息TOPIC改为事务消息TOPIC
        putMessageResult = this.brokerController.getTransactionalMessageService().prepareMessage(msgInner);
    } else {
        putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);
    }

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

}
复制代码

终于找到最有价值的方法 先来看看这个方法 然后再继续向下看

核心逻辑

  • 创建RemotingCommand 响应命令 并设置参数 以及进行检查
  • 获取对应的topic配置 用于判断是否属于重试topic
  • 构建 MessageExtBrokerInner
  • 判断是否为事务消息 如果是进行使用事务消息特定的方式方式 否则普通消息处理发送
  • 返回处理结果 也就是存入commitLog

接着看看调用 org.apache.rocketmq.broker.processor.SendMessageProcessor#processRequest

@Override
public RemotingCommand processRequest(ChannelHandlerContext ctx,
                                      RemotingCommand request) throws RemotingCommandException {
    SendMessageContext mqtraceContext;
    switch (request.getCode()) {
        case RequestCode.CONSUMER_SEND_MSG_BACK:
            return this.consumerSendMsgBack(ctx, request);
        default:
            SendMessageRequestHeader requestHeader = parseRequestHeader(request);
            if (requestHeader == null) {
                return null;
            }

            mqtraceContext = buildMsgContext(ctx, requestHeader);
            this.executeSendMessageHookBefore(ctx, request, mqtraceContext);

            RemotingCommand response;
            if (requestHeader.isBatch()) {
                response = this.sendBatchMessage(ctx, request, mqtraceContext, requestHeader);
            } else {
                response = this.sendMessage(ctx, request, mqtraceContext, requestHeader);
            }

            this.executeSendMessageHookAfter(response, mqtraceContext);
            return response;
    }
}
复制代码

核心逻辑

  • 根据request的code进行处理不同逻辑 判断是否为消费者发送回调 是的话进行调用 否则进行其他处理
  • 解析请求头 构建消息上下文 判断消息请求命令是否为批量 如果是批量命令则进行调用批量发送 否则进行单个发送
  • 返回发送结果

org.apache.rocketmq.remoting.netty.NettyRemotingAbstract.processRequestCommand

public void processRequestCommand(final ChannelHandlerContext ctx, final RemotingCommand cmd) {
    final Pair<NettyRequestProcessor, ExecutorService> matched = this.processorTable.get(cmd.getCode());
    final Pair<NettyRequestProcessor, ExecutorService> pair = null == matched ? this.defaultRequestProcessor : matched;
    final int opaque = cmd.getOpaque();

    if (pair != null) {
        Runnable run = new Runnable() {
            @Override
            public void run() {
                try {
                    doBeforeRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd);
                    final RemotingCommand response = pair.getObject1().processRequest(ctx, cmd);
                    doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd, response);

                    if (!cmd.isOnewayRPC()) {
                        if (response != null) {
                            response.setOpaque(opaque);
                            response.markResponseType();
                            try {
                                ctx.writeAndFlush(response);
                            } catch (Throwable e) {
                                log.error("process request over, but response failed", e);
                                log.error(cmd.toString());
                                log.error(response.toString());
                            }
                        } else {

                        }
                    }
                } catch (Throwable e) {
                    log.error("process request exception", e);
                    log.error(cmd.toString());

                    if (!cmd.isOnewayRPC()) {
                        final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_ERROR,
                                RemotingHelper.exceptionSimpleDesc(e));
                        response.setOpaque(opaque);
                        ctx.writeAndFlush(response);
                    }
                }
            }
        };
    }
}
复制代码

核心逻辑

  • 根据请求code从缓存中获取请求处理器 不存在则使用默认的
  • 处理请求

org.apache.rocketmq.remoting.netty.NettyRemotingAbstract.processMessageReceived

public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
    final RemotingCommand cmd = msg;
    if (cmd != null) {
        switch (cmd.getType()) {
            case REQUEST_COMMAND:
                processRequestCommand(ctx, cmd);
                break;
            case RESPONSE_COMMAND:
                processResponseCommand(ctx, cmd);
                break;
            default:
                break;
        }
    }
}
复制代码

核心逻辑

  • 这个逻辑就很清晰了 根据远程命令类型来进行处理 一种是请求命令 一种是响应命令

接下来就更为清晰了

org.apache.rocketmq.remoting.netty.NettyRemotingServer.NettyServerHandler.channelRead0 org.apache.rocketmq.remoting.netty.NettyRemotingClient.NettyClientHandler.channelRead0

@ChannelHandler.Sharable
class NettyServerHandler extends SimpleChannelInboundHandler<RemotingCommand> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
        processMessageReceived(ctx, msg);
    }
}
class NettyClientHandler extends SimpleChannelInboundHandler<RemotingCommand> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
        processMessageReceived(ctx, msg);
    }
}

复制代码

核心逻辑

  • netty的入栈处理器继承了SimpleChannelInboundHandler 服务端负责处理请求 客户端同理

以上就是half消息的构建流程 主体思路理一下

  • 负责服务端的nettyHandler接收处理请求
  • 核心处理方法 processMessageReceived 根据请求类型判断是 请求命令 还是响应命令
  • 处理请求命令 processRequestCommand 从缓存中获取请求code对应的处理器 如果不存在则使用默认的
  • 处理请求 processRequest 首先判断是否为 消费者发送消息回调 如果是进行处理 否则 解析请求头 构造消息上下文 判断是否为批量消息发送 如果是批量则进行批量消息发送 否则单条消息发送
  • 这里重点关注 单消息发送sendMessage方法 首先创建响应命令 获取自定义的响应头 然后做一些响应头属性相关设置 接着构建 MessageExtBrokerInner 判断是否为事务消息 如果不是事务消息则直接 落盘处理 事务消息则特殊处理
  • 事务消息的特殊处理 prepareMessage 主要调用 transactionalMessageBridge的putHalfMessage方法放入half消息
  • transactionalMessageBridge的putHalfMessage方法 调用默认的 消息储存服务 store的putMessage方法 重点在参数中还调用parseHalfMessageInner(messageInner)
  • 这个parseHalfMessageInner方法就很关键 保存真实的topic和queueId 同时修改消息topic为half消息主体

这就完成了整个事务消息的改为half topic 并存入commitLog流程

到这里事务消息还有个关键的地方 还存在了Op消息 Op消息标识事务消息已经确定的状态(Commit或者Rollback) 为此再来看看Op消息的构建流程 同样也是从TransactionalMessageUtil开始 org.apache.rocketmq.broker.transaction.queue.TransactionalMessageUtil#buildOpTopic

public static String buildOpTopic() {
    return MixAll.RMQ_SYS_TRANS_OP_HALF_TOPIC;
}
复制代码

找到了存入Op消息入口 org.apache.rocketmq.broker.transaction.queue.TransactionalMessageBridge#putOpMessage

public boolean putOpMessage(MessageExt messageExt, String opType) {
    MessageQueue messageQueue = new MessageQueue(messageExt.getTopic(),
            this.brokerController.getBrokerConfig().getBrokerName(), messageExt.getQueueId());
    if (TransactionalMessageUtil.REMOVETAG.equals(opType)) {
        //移除类型 则早事务OP添加移除标记
        return addRemoveTagInTransactionOp(messageExt, messageQueue);
    }
    return true;
}
复制代码

找到调用者

org.apache.rocketmq.broker.transaction.queue.TransactionalMessageServiceImpl#deletePrepareMessage

@Override
public boolean deletePrepareMessage(MessageExt msgExt) {
    if (this.transactionalMessageBridge.putOpMessage(msgExt, TransactionalMessageUtil.REMOVETAG)) {
        //注意传入的类型为REMOVETAG putOpMessage方法中addRemoveTagInTransactionOp 会被调用 
        log.debug("Transaction op message write successfully. messageId={}, queueId={} msgExt:{}", msgExt.getMsgId(), msgExt.getQueueId(), msgExt);
        return true;
    } else {
        log.error("Transaction op message write failed. messageId is {}, queueId is {}", msgExt.getMsgId(), msgExt.getQueueId());
        return false;
    }
}
复制代码

再来看看putOpMessage方法中addRemoveTagInTransactionOp

private boolean addRemoveTagInTransactionOp(MessageExt messageExt, MessageQueue messageQueue) {
    Message message = new Message(TransactionalMessageUtil.buildOpTopic(), TransactionalMessageUtil.REMOVETAG,
            String.valueOf(messageExt.getQueueOffset()).getBytes(TransactionalMessageUtil.charset));
    //写入OP消息
    writeOp(message, messageQueue);
    return true;
}

private void writeOp(Message message, MessageQueue mq) {
    MessageQueue opQueue;
    if (opQueueMap.containsKey(mq)) {
        opQueue = opQueueMap.get(mq);
    } else {
        opQueue = getOpQueueByHalf(mq);
        MessageQueue oldQueue = opQueueMap.putIfAbsent(mq, opQueue);
        if (oldQueue != null) {
            opQueue = oldQueue;
        }
    }
    if (opQueue == null) {
        opQueue = new MessageQueue(TransactionalMessageUtil.buildOpTopic(), mq.getBrokerName(), mq.getQueueId());
    }
    putMessage(makeOpMessageInner(message, opQueue));
}

private MessageExtBrokerInner makeOpMessageInner(Message message, MessageQueue messageQueue) {
    MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
    msgInner.setTopic(message.getTopic());
    msgInner.setBody(message.getBody());
    msgInner.setQueueId(messageQueue.getQueueId());
    msgInner.setTags(message.getTags());
    msgInner.setTagsCode(MessageExtBrokerInner.tagsString2tagsCode(msgInner.getTags()));
    msgInner.setSysFlag(0);
    MessageAccessor.setProperties(msgInner, message.getProperties());
    msgInner.setPropertiesString(MessageDecoder.messageProperties2String(message.getProperties()));
    msgInner.setBornTimestamp(System.currentTimeMillis());
    msgInner.setBornHost(this.storeHost);
    msgInner.setStoreHost(this.storeHost);
    msgInner.setWaitStoreMsgOK(false);
    MessageClientIDSetter.setUniqID(msgInner);
    return msgInner;
}

public boolean putMessage(MessageExtBrokerInner messageInner) {
    PutMessageResult putMessageResult = store.putMessage(messageInner);
    if (putMessageResult != null
            && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) {
        return true;
    } else {
        LOGGER.error("Put message failed, topic: {}, queueId: {}, msgId: {}",
                messageInner.getTopic(), messageInner.getQueueId(), messageInner.getMsgId());
        return false;
    }
}
复制代码

核心逻辑

  • addRemoveTagInTransactionOp方法 创建message对象 topic为op消息对应的topic
  • 写入op消息 writeOp 判断OP队列缓存中是否存在 不存在则根据half获取队列 并且放入缓存 调用存储服务存入commitLog
  • makeOpMessageInner 构建MessageExtBrokerInner对应
  • putMessage 调用存储服务将该消息存入commitLog

接着向上寻找 找 deletePrepareMessage方法的调用 org.apache.rocketmq.broker.processor.EndTransactionProcessor#processRequest

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

    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()) {
        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());
                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;
}
复制代码

核心逻辑

  • 判断来自事务检查 是则 进行执行操作
  • 判断事务状态 是提交 还是回滚 还是pending状态
  • 构造OperationResult 根据提交事务还是回滚事务进行提交或者回滚消息
    • 提交事务
      • 检查准备消息 返回remotingCommand
      • 返回成功状态
        • 结束消息事务 endMessageTransaction 使用之前存储的真实topic和queueId重新构建一个新的消息
        • 刷盘处理 写入到commitLog
        • 刷盘成功 进行删除 deletePrepareMessage
    • 回滚事务
      • 检查准备消息 返回remotingCommand
      • 返回成功状态
        • 不需要做什么 因为真实的消息一直还没落盘 故也不需要删除
        • 进行删除 deletePrepareMessage
  • 返回OperationResult结果

又回到熟悉的processRequestCommand 处理请求命令 org.apache.rocketmq.remoting.netty.NettyRemotingAbstract.processRequestCommand

public void processRequestCommand(final ChannelHandlerContext ctx, final RemotingCommand cmd) {
      final Pair<NettyRequestProcessor, ExecutorService> matched = this.processorTable.get(cmd.getCode());
      final Pair<NettyRequestProcessor, ExecutorService> pair = null == matched ? this.defaultRequestProcessor : matched;
      final int opaque = cmd.getOpaque();

      if (pair != null) {
         Runnable run = new Runnable() {
            @Override
            public void run() {
               try {
                  doBeforeRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd);
                  final RemotingCommand response = pair.getObject1().processRequest(ctx, cmd);
                  doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd, response);
                        //.....
                    }
                }
        }
}
复制代码

又和上述流程一致了

org.apache.rocketmq.remoting.netty.NettyRemotingAbstract.processMessageReceived 在往上又是进入到 org.apache.rocketmq.remoting.netty.NettyRemotingClient.NettyClientHandler org.apache.rocketmq.remoting.netty.NettyRemotingServer.NettyServerHandler 处理 不是此处应该是进入到 NettyServerHandler 进行事务的结束处理. 最后由producer生产者发送提交或者回滚事务给broker 也就是server进行处理 server接收到这个请求 最终调用EndTransactionProcessor的processRequest方法进行处理事务请求.

看图重新理解一下 下面来自官方文档描述 事务消息流程

上图说明了事务消息的大致方案,其中分为两个流程:正常事务消息的发送及提交、事务消息的补偿流程。

1.事务消息发送及提交:

(1) 发送消息(half消息)。

(2) 服务端响应消息写入结果。

(3) 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)。

(4) 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)

2.补偿流程:

(1) 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”

(2) Producer收到回查消息,检查回查消息对应的本地事务的状态

(3) 根据本地事务状态,重新Commit或者Rollback

其中,补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。

RocketMQ事务消息设计

1.事务消息在一阶段对用户不可见

在RocketMQ事务消息的主要流程中,一阶段的消息如何对用户不可见。其中,事务消息相对普通消息最大的特点就是一阶段发送的消息对用户是不可见的。那么,如何做到写入消息但是对用户不可见呢?RocketMQ事务消息的做法是:如果消息是half消息,将备份原消息的主题与消息消费队列,然后改变主题为RMQ_SYS_TRANS_HALF_TOPIC。由于消费组未订阅该主题,故消费端无法消费half类型的消息,然后RocketMQ会开启一个定时任务,从Topic为RMQ_SYS_TRANS_HALF_TOPIC中拉取消息进行消费,根据生产者组获取一个服务提供者发送回查事务状态请求,根据事务状态来决定是提交或回滚消息。

在RocketMQ中,消息在服务端的存储结构如下,每条消息都会有对应的索引信息,Consumer通过ConsumeQueue这个二级索引来读取消息实体内容,其流程如下: 消息消费流程

RocketMQ的具体实现策略是:写入的如果事务消息,对消息的Topic和Queue等属性进行替换,同时将原来的Topic和Queue信息存储到消息的属性中,正因为消息主题被替换,故消息并不会转发到该原主题的消息消费队列,消费者无法感知消息的存在,不会消费。其实改变消息主题是RocketMQ的常用“套路”,回想一下延时消息的实现机制。

2.Commit和Rollback操作以及Op消息的引入

在完成一阶段写入一条对用户不可见的消息后,二阶段如果是Commit操作,则需要让消息对用户可见;如果是Rollback则需要撤销一阶段的消息。先说Rollback的情况。对于Rollback,本身一阶段的消息对用户是不可见的,其实不需要真正撤销消息(实际上RocketMQ也无法去真正的删除一条消息,因为是顺序写文件的)。但是区别于这条消息没有确定状态(Pending状态,事务悬而未决),需要一个操作来标识这条消息的最终状态。RocketMQ事务消息方案中引入了Op消息的概念,用Op消息标识事务消息已经确定的状态(Commit或者Rollback)。如果一条事务消息没有对应的Op消息,说明这个事务的状态还无法确定(可能是二阶段失败了)。引入Op消息后,事务消息无论是Commit或者Rollback都会记录一个Op操作。Commit相对于Rollback只是在写入Op消息前创建Half消息的索引。

3.Op消息的存储和对应关系

RocketMQ将Op消息写入到全局一个特定的Topic中通过源码中的方法—TransactionalMessageUtil.buildOpTopic();这个Topic是一个内部的Topic(像Half消息的Topic一样),不会被用户消费。Op消息的内容为对应的Half消息的存储的Offset,这样通过Op消息能索引到Half消息进行后续的回查操作。

half消息

4.Half消息的索引构建

在执行二阶段Commit操作时,需要构建出Half消息的索引。一阶段的Half消息由于是写到一个特殊的Topic,所以二阶段构建索引时需要读取出Half消息,并将Topic和Queue替换成真正的目标的Topic和Queue,之后通过一次普通消息的写入操作来生成一条对用户可见的消息。所以RocketMQ事务消息二阶段其实是利用了一阶段存储的消息的内容,在二阶段时恢复出一条完整的普通消息,然后走一遍消息写入流程。

5.如何处理二阶段失败的消息?

如果在RocketMQ事务消息的二阶段过程中失败了,例如在做Commit操作时,出现网络问题导致Commit失败,那么需要通过一定的策略使这条消息最终被Commit。RocketMQ采用了一种补偿机制,称为“回查”。Broker端对未确定状态的消息发起回查,将消息发送到对应的Producer端(同一个Group的Producer),由Producer根据消息来检查本地事务的状态,进而执行Commit或者Rollback。Broker端通过对比Half消息和Op消息进行事务消息的回查并且推进CheckPoint(记录那些事务消息的状态是确定的)。

值得注意的是,rocketmq并不会无休止的的信息事务状态回查,默认回查15次,如果15次回查还是无法得知事务状态,rocketmq默认回滚该消息。

end 感谢各位查阅

猜你喜欢

转载自juejin.im/post/7030983017199828999