事务消息之二阶段提交机制

453 阅读2分钟

图解事务消息

事务消息通过消息二次提交机制,可用于解决分布式事务问题。

如何发送事务消息

/**
 * 发送事务消息
 */
@Test
public void sendTrasitionMessage() throws Exception {

    // 创建事务的消息提供者
    TransactionMQProducer producer = new TransactionMQProducer("group");
    // 设置namesrv地址
    producer.setNamesrvAddr("127.0.0.1:9876");
    
    // 设置本地事务的检查方法
    producer.setTransactionListener(new TransactionListener() {
        /**
         * @param message 消息对象
         * @param o
         * @return 事务状态
         */
        @Override
        public LocalTransactionState executeLocalTransaction(Message message, Object o) {
            if (message.getTags().equals("TAG1")) {
                // 本地事务执行完成,提交消息事务
                return LocalTransactionState.COMMIT_MESSAGE;
            }else if (message.getTags().equals("TAG2")) {
                // 本地事务执行失败,回滚消息事务
                return LocalTransactionState.ROLLBACK_MESSAGE;
            }else if (message.getTags().equals("TAG3")) {
                // 消息状态未知
                return LocalTransactionState.UNKNOW;
            }
            return null;
        }

        /**
         *  消息状态为UNKNOW时的回查函数,检查本地事务的执行情况
         */
        @Override
        public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
            System.out.println(messageExt.getBody());
            return LocalTransactionState.COMMIT_MESSAGE;
        }
    });
    // 开启生产者
    producer.start();
    // 创建消息体
    Message mesage = new Message("Topic","TAG1","myMsg".getBytes());
    // 发送事务消息
    producer.sendMessageInTransaction(mesage,null);
    producer.shutdown();
}

事务状态

COMMIT_MESSAGE:     本地事务已提交,允许消费者消费该消息
ROLLBACK_MESSAGE:   本地事务回滚,删除该消息
UNKONW:             事务状态未知,broker需要回查本地事务

注意事项:

1.事务消息不支持延迟特性(源码会清空延迟等级)
// ignore DelayTimeLevel parameter
if (msg.getDelayTimeLevel() != 0) {
    MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_DELAY_TIME_LEVEL);
}
2.消息不能保证只被检查或消费一次,因此消费方需要做幂等处理
3.不支持批量发送

事务消息二次提交机制(源码分析):

1.生产者发送消息给broker              -- 事务消息存放在特定队列中,此时消费不到
// 清空延迟消息的level
if (msg.getDelayTimeLevel() != 0) {
    MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_DELAY_TIME_LEVEL);
}

SendResult sendResult = null;
// 设置为预消息
MessageAccessor.putProperty(msg, 
                    MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
MessageAccessor.putProperty(msg, 
                    MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
try {
    // 发送half半消息(prepared消息)
    sendResult = this.send(msg);
} catch (Exception e) {
    throw new MQClientException("send message Exception", e);
}


2.发送半消息成功之后,执行本地事务
switch (sendResult.getSendStatus()) {
    case SEND_OK: {

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

3.将本地事务状态通过请求头传给broker
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;
}

requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());
requestHeader.setMsgId(sendResult.getMsgId());
String remark = localException != null ? ("executeLocalTransactionBranch exception: " + localException.toString()) : null;
// 将事务状态传给broker
endTransactionOneway(brokerAddr, requestHeader, remark,
        this.defaultMQProducer.getSendMsgTimeout());
    

4.若本地事务提交,broker则提交该消息。 -- 将事务消息推送到真实队列中

Broker端事务提交/回滚操作(这里取endTransaction部分)
代码入口:org.apache.rocketmq.broker.processor.EndTransactionProcessor

if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) {
    result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);
    // 将msg的目标队列 设置为真实的队列/主题
    MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage());
    MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED);
    // 并发送最终的消息到真实队列
    RemotingCommand sendResult = sendFinalMessage(msgInner);
    if (sendResult.getCode() == ResponseCode.SUCCESS) {
        // 删除原事务队列的消息
        this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
    }
    return sendResult

}
4.若本地事务回滚,broker则删除消息。   -- 从特定事务队列中删除该消息

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

5.若因网络等原因导致事务状态为UNKNOW,broker回查未知的消息。
6.定时回查事务状态。 -- 若失败,则删除消息,若事务成功,则提交消息到真实队列。