RocketMQソースコードの調査[1]-プロデューサーの観点からのトランザクションニュース

序文

有名なオープンソースメッセージングミドルウェアであるApacheRocketMQは、Alibabaで生まれ、2016年にApacheに寄付されました。RocketMQ 4.0から最新のv4.7.1まで、Alibabaの内部コミュニティでも外部コミュニティでも、広く注目され、賞賛されています。
興味と仕事の必要性から、私は最近RocketMQ 4.7.1のコードの一部を研究しました。これは多くの混乱を引き起こし、より多くのインスピレーションを得ました。

この記事では、送信者の観点から、RocketMQプロデューサーのソースコードを読んで、トランザクションメッセージ送信でRocketMQがどのように機能するかを分析します。この記事に投稿されているコードは、バージョン4.7.1のRocketMQソースコードからのものであることに注意してください。この記事で説明する送信は、プロデューサーからブローカーに送信するプロセスのみを指し、ブローカーがコンシューマーにメッセージを配信するプロセスは含まれていません。

マクロの概要

RocketMQトランザクションメッセージ送信プロセス:

RocketMQのトランザクションメッセージTransactionMQProducerのsendMessageInTransactionメソッドは、ソースコードと組み合わせて、実際にはDefaultMQProducerImplのsendMessageInTransactionメソッドを呼び出します。sendMessageInTransactionメソッドを入力すると、トランザクションメッセージ全体の送信プロセスが明確に表示されます。

まず、送信する前に確認し、トランザクションの準備メッセージを含む必要なパラメータを入力します。

ソースリスト-1

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

    // ignore DelayTimeLevel parameter
    if (msg.getDelayTimeLevel() != 0) {
        MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_DELAY_TIME_LEVEL);
    }

    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());

送信プロセスに入ります。

ソースリスト-2

    try {
        sendResult = this.send(msg);
    } catch (Exception e) {
        throw new MQClientException("send message Exception", e);
    }

ブローカーから返された処理結果に従ってローカルトランザクションを実行するかどうかを決定し、ハーフメッセージが正常に送信されたらローカルトランザクションの実行を開始します。

ソースリスト-3

    LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
    Throwable localException = null;
    switch (sendResult.getSendStatus()) {
        case SEND_OK: {
            try {
                if (sendResult.getTransactionId() != null) {
                    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:  // 当备broker状态不可用时,半消息要回滚,不执行本地事务
            localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
            break;
        default:
            break;
    }

ローカルトランザクションの実行が終了し、ローカルトランザクションのステータスに応じて2段階の処理が実行されます。

ソースリスト-4

    try {
        this.endTransaction(sendResult, localTransactionState, localException);
    } catch (Exception e) {
        log.warn("local transaction execute " + localTransactionState + ", but end broker transaction failed", e);
    }

    // 组装发送结果
    // ...
    return transactionSendResult;
}

次に、各段階で詳細なコード分析を行います。

心の奥底で

一段階配信

sendメソッドに焦点を当てます。sendメソッドに入った後、RocketMQトランザクションメッセージの最初のステージがSYNC同期モードを使用していることがわかりました。

ソースリスト-5

public SendResult send(Message msg,
    long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    return this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout);
}

これは理解しやすいです。結局のところ、トランザクションメッセージは、ローカルトランザクションを実行するかどうかを決定するステージの結果に基づいているため、ブローカーの確認応答を待ってブロックする必要があります。

DefaultMQProducerImpl.javaと入力して、sendDefaultImplメソッドの実装を確認しましょう。このメソッドのコードを読み取ることで、トランザクションメッセージ送信プロセスの最初の段階でのプロデューサーの動作を理解しようとします。このメソッドはトランザクションメッセージ用にカスタマイズされておらず、SYNC同期モード用にもカスタマイズされていないため、このコードを読んだ後は、基本的にRocketMQのメッセージ送信メカニズムをより包括的に理解できます。
このコードのロジックは非常にスムーズで、スライスするのに耐えられません。スペースを節約するために、コードのより複雑で情報量の少ない部分をコメントに置き換えて、プロセスの整合性を可能な限り維持します。私が個人的に重要だと思う部分や見落としがちな部分にはメモが付いており、詳細については後で詳しく説明します。

ソースリスト-6

private SendResult sendDefaultImpl(
    Message msg,
    final CommunicationMode communicationMode,
    final SendCallback sendCallback,
    final long timeout
    ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    this.makeSureStateOK();
    // 一、消息有效性校验。见后文
    Validators.checkMessage(msg, this.defaultMQProducer);
    final long invokeID = random.nextLong();
    long beginTimestampFirst = System.currentTimeMillis();
    long beginTimestampPrev = beginTimestampFirst;
    long endTimestamp = beginTimestampFirst;

    // 获取当前topic的发送路由信息,主要是要broker,如果没找到则从namesrv获取
    TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
    if (topicPublishInfo != null && topicPublishInfo.ok()) {
        boolean callTimeout = false;
        MessageQueue mq = null;
        Exception exception = null;
        SendResult sendResult = null;
        // 二、发送重试机制。见后文
        int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
        int times = 0;
        String[] brokersSent = new String[timesTotal];
        for (; times < timesTotal; times++) {
            // 第一次发送是mq == null, 之后都是有broker信息的
            String lastBrokerName = null == mq ? null : mq.getBrokerName();
            // 三、rocketmq发送消息时如何选择队列?——broker异常规避机制 
            MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);

            if (mqSelected != null) {
                mq = mqSelected;
                brokersSent[times] = mq.getBrokerName();
                try {
                    beginTimestampPrev = System.currentTimeMillis();
                    if (times > 0) {
                        //Reset topic with namespace during resend.
                        msg.setTopic(this.defaultMQProducer.withNamespace(msg.getTopic()));
                    }
                    long costTime = beginTimestampPrev - beginTimestampFirst;
                    if (timeout < costTime) {
                        callTimeout = true;
                        break;
                    }
                    // 发送核心代码
                    sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
                    endTimestamp = System.currentTimeMillis();
                    // rocketmq 选择 broker 时的规避机制,开启 sendLatencyFaultEnable == true 才生效
                    this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);

                    switch (communicationMode) {
                    // 四、RocketMQ的三种CommunicationMode。见后文
                        case ASYNC: // 异步模式
                            return null;
                        case ONEWAY: // 单向模式
                            return null;
                        case SYNC: // 同步模式
                            if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
                                if (this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) {
                                    continue;
                                }
                            }
                            return sendResult;
                        default:
                            break;
                    }
                } catch (RemotingException e) {
                    // ...
                    // 自动重试
                } catch (MQClientException e) {
                    // ...
                    // 自动重试
                } catch (MQBrokerException e) {
                   // ...
                    // 仅返回码==NOT_IN_CURRENT_UNIT==205 时自动重试
                    // 其他情况不重试,抛异常
                } catch (InterruptedException e) {
                   // ...
                    // 不重试,抛异常
                }
            } else {
                break;
            }
        }

        if (sendResult != null) {
            return sendResult;
        }

        // 组装返回的info信息,最后以MQClientException抛出
        // ... ...

        // 超时场景抛RemotingTooMuchRequestException
        if (callTimeout) {
            throw new RemotingTooMuchRequestException("sendDefaultImpl call timeout");
        }

        // 填充MQClientException异常信息
        // ...
    }

    validateNameServerSetting();

    throw new MQClientException("No route info of this topic: " + msg.getTopic() + FAQUrl.suggestTodo(FAQUrl.NO_TOPIC_ROUTE_INFO),
        null).setResponseCode(ClientErrorCode.NOT_FOUND_TOPIC_EXCEPTION);
}

1.メッセージの有効性チェック:

ソースリスト-7

 Validators.checkMessage(msg, this.defaultMQProducer);

この方法では、トピックとメッセージ本文の検証を含め、メッセージの有効性が検証されます。トピックの命名は仕様に準拠している必要があり、組み込みのシステムメッセージTOPICの使用は避けてください。メッセージ本文の長さ> 0 &&メッセージ本文の長さ<= 1024 * 1024 * 4 = 4M。

ソースリスト-8

public static void checkMessage(Message msg, DefaultMQProducer defaultMQProducer)
    throws MQClientException {
    if (null == msg) {
        throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message is null");
    }
    // topic
    Validators.checkTopic(msg.getTopic());
    Validators.isNotAllowedSendTopic(msg.getTopic());

    // body
    if (null == msg.getBody()) {
        throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message body is null");
    }

    if (0 == msg.getBody().length) {
        throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message body length is zero");
    }

    if (msg.getBody().length > defaultMQProducer.getMaxMessageSize()) {
        throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL,
            "the message body size over max value, MAX: " + defaultMQProducer.getMaxMessageSize());
    }
}

2、再試行メカニズムを送信します

メッセージが正常に送信されなかった場合、プロデューサーは自動的に再試行します。送信回数の最大値= restartTimesWhenSendFailed + 1 = 3回。

すべての異常な状況が再試行されるわけではないことに注意してください。上記のソースコードから抽出できる情報は、次の3つの状況で自動的に再試行することを示しています
。1)2つの例外のいずれかが発生するとRemotingExceptionまたはMQClientExceptionが発生する
2 )MQBrokerExceptionが発生し、ResponseCodeがNOT_IN_CURRENT_UNIT = 205の場合
3)SYNCモードでは、例外は発生せず、送信結果のステータスはSEND_OKではありません。

各メッセージが送信される前に、最初に前の2つの手順に時間がかかったかどうかがチェックされます(タイムアウト期間はデフォルトで3000msです)。そうであれば、メッセージは送信を続行せず、再試行せずにタイムアウトに戻ります。ここでは、2つの問題について説明します
。1)プロデューサー内の自動再試行はビジネスアプリケーションには認識されず、アプリケーションに表示される送信時間には、すべての再試行に費やされた時間が含まれます
。2)タイムアウトすると、このメッセージ送信は次のようになります。タイムアウトのために失敗しました。このメッセージは、最終的にRemotingTooMuchRequestExceptionの形式でスローされます。

ここで指摘する必要があるのは、RocketMQの公式文書では、送信タイムアウト時間は10秒、つまり10000ミリ秒であると指摘されていることです。インターネット上の多くの人々も、rocketMQのタイムアウト時間は10秒であると考えています。ただし、コードには3000msが明確に記述されており、デバッグ後、デフォルトのタイムアウト期間が実際に3000msであることを最終的に確認しました。また、RocketMQチームがドキュメントを確認することをお勧めします。間違いがある場合は、できるだけ早く修正することをお勧めします。
RocketMQソースコードの調査[1]-プロデューサーの観点からのトランザクションニュース

第三に、ブローカーの異常な回避メカニズム

ソースリスト-8

MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);  

このコード行は、送信する前にキューを選択するプロセスです。

これには、高可用性、latencyFaultToleranceを送信するRocketMQメッセージのコアメカニズムが含まれます。このメカニズムはプロデューサーの負荷分散の一部であり、sendLatencyFaultEnableの値によって制御されます。デフォルトはfalseで、ブローカー障害遅延メカニズムはアクティブ化されていません。値がtrueの場合、ブローカー障害遅延メカニズムが有効になり、アクティブ化できます。プロデューサーによる。

キューを選択するときは、異常な従来の回避メカニズムをオンにし、ブローカーの動作状態に応じて、現在の状態のブローカーエージェントを選択しないようにします。異常なブローカーは一定期間回避されます。異常な従来の回避メカニズムの場合が有効になっていない場合は、次のキューが順番に選択されますが、再試行シナリオでは、前回送信されたブローカーとは異なるキューを選択してみてください。メッセージが送信されるたびに、ブローカーのステータス情報はupdateFaultItemメソッドを介して維持されます。

ソースリスト-9

public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation) {
    if (this.sendLatencyFaultEnable) {
        // 计算延迟多久,isolation表示是否需要隔离该broker,若是,则从30s往前找第一个比30s小的延迟值,再按下标判断规避的周期,若30s,则是10min规避;
        // 否则,按上一次发送耗时来决定规避时长;
        long duration = computeNotAvailableDuration(isolation ? 30000 : currentLatency);
        this.latencyFaultTolerance.updateFaultItem(brokerName, currentLatency, duration);
    }
}  

selectOneMessageQueueメソッドを詳しく調べて、次のことを確認してください。

ソースリスト-10

public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
    if (this.sendLatencyFaultEnable) {
        // 开启异常规避
        try {
            int index = tpInfo.getSendWhichQueue().getAndIncrement();
            for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
                int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
                if (pos < 0)
                    pos = 0;
                // 按顺序取下一个message queue作为发送的queue
                MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
                // 当前queue所在的broker可用,且与上一个queue的broker相同,
                // 或者第一次发送,则使用这个queue
                if (latencyFaultTolerance.isAvailable(mq.getBrokerName())) {
                    if (null == lastBrokerName || mq.getBrokerName().equals(lastBrokerName))
                        return mq;
                }
            }

            final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
            int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
            if (writeQueueNums > 0) {
                final MessageQueue mq = tpInfo.selectOneMessageQueue();
                if (notBestBroker != null) {
                    mq.setBrokerName(notBestBroker);
                    mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);
                }
                return mq;
            } else {
                latencyFaultTolerance.remove(notBestBroker);
            }
        } catch (Exception e) {
            log.error("Error occurred when selecting message queue", e);
        }

        return tpInfo.selectOneMessageQueue();
    }
    // 不开启异常规避,则随机自增选择Queue
    return tpInfo.selectOneMessageQueue(lastBrokerName);
}

4、RocketMQの3つのCommunicationMode:

ソースリスト-11

 public enum CommunicationMode {
    SYNC,
    ASYNC,
    ONEWAY,
}

上記の3つのモードは、メッセージが送信者からブローカーに到達する段階を示しており、ブローカーがサブスクライバーにメッセージを配信するプロセスは含まれていません。
送信の3つのモードの違い:

  • 一方向モード:一方向メッセージ送信者はそれを送信するだけで、ブローカー処理の結果を気にしません。このモードでは、処理フローが小さいため、送信時間が非常に短く、スループットが大きくなりますが、メッセージの信頼性と損失がないことを保証することはできません。トラフィックが多いが重要なメッセージではないシナリオでよく使用されます。 、ハートビート送信など。
  • 非同期モード: ASYNC。メッセージの送信者がブローカーにメッセージを送信した後、ブローカーがメッセージを処理するのを待つ必要はありません。代わりに、非同期スレッドがメッセージの処理を行います。処理が完了すると、送信者に送信結果が通知されます。コールバックの形式。非同期処理中に例外が発生した場合、送信者の失敗結果を返す前に内部で再試行されます(デフォルトでは3回、送信者は認識しません)。このモードでは、送信者の待ち時間が短く、スループットが大きく、メッセージの信頼性が高く、重いが重要なメッセージのシーンで使用されます。
  • 同期モード: SYNC。メッセージ送信者は、ブローカーが処理を完了し、成功または失敗を明示的に返すのを待つ必要があります。メッセージ送信者がメッセージ送信の失敗の結果を取得する前に、内部再試行も発生します(デフォルトでは3回、送信者は認識しません) 。このモードでは、送信者はメッセージ処理の結果をブロックして待機し、待機時間が長く、メッセージの信頼性が高く、トラフィックが少ない重要なメッセージシナリオに使用されます。トランザクションメッセージの1フェーズおよびハーフトランザクションメッセージの処理は同期モードであることを強調しておく必要があります。

特定の実装の違いは、sendKernelImplメソッドでも確認できます。ONEWAYモードは最も単純で、処理を行いません。送信を担当するsendMessageメソッドのパラメーターの中で、同期モードと比較して、非同期モードには、より多くのコールバックメソッド、トピック送信ルーティングメタ情報を含むtopicPublishInfo、送信ブローカー情報を含むインスタンス、送信キュー情報を含むプロデューサー、および再試行します。さらに、非同期モードでは、圧縮されたメッセージが最初にコピーされます。

ソースリスト-12

    switch (communicationMode) {
                case ASYNC:
                    Message tmpMessage = msg;
                    boolean messageCloned = false;
                    if (msgBodyCompressed) {
                        //If msg body was compressed, msgbody should be reset using prevBody.
                        //Clone new message using commpressed message body and recover origin massage.
                        //Fix bug:https://github.com/apache/rocketmq-externals/issues/66
                        tmpMessage = MessageAccessor.cloneMessage(msg);
                        messageCloned = true;
                        msg.setBody(prevBody);
                    }

                    if (topicWithNamespace) {
                        if (!messageCloned) {
                            tmpMessage = MessageAccessor.cloneMessage(msg);
                            messageCloned = true;
                        }
                        msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQProducer.getNamespace()));
                    }

                    long costTimeAsync = System.currentTimeMillis() - beginStartTime;
                    if (timeout < costTimeAsync) {
                        throw new RemotingTooMuchRequestException("sendKernelImpl call timeout");
                    }
                    sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
                        brokerAddr,
                        mq.getBrokerName(),
                        tmpMessage,
                        requestHeader,
                        timeout - costTimeAsync,
                        communicationMode,
                        sendCallback,
                        topicPublishInfo,
                        this.mQClientFactory,
                        this.defaultMQProducer.getRetryTimesWhenSendAsyncFailed(),
                        context,
                        this);
                    break;
                case ONEWAY:
                case SYNC:
                    long costTimeSync = System.currentTimeMillis() - beginStartTime;
                    if (timeout < costTimeSync) {
                        throw new RemotingTooMuchRequestException("sendKernelImpl call timeout");
                    }
                    sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
                        brokerAddr,
                        mq.getBrokerName(),
                        msg,
                        requestHeader,
                        timeout - costTimeSync,
                        communicationMode,
                        context,
                        this);
                    break;
                default:
                    assert false;
                    break;
            } 

公式文書にはそのような写真があり、非同期通信の詳細なプロセスを明確に説明しています。
RocketMQソースコードの調査[1]-プロデューサーの観点からのトランザクションニュース

2段階配信

ソースコードlisting-3は、ローカルトランザクションの実行を反映しています。localTransactionStateは、ローカルトランザクションの実行結果をトランザクションメッセージの第2フェーズの送信に関連付けます。
最初のステージの送信結果がSLAVE_NOT_AVAILABLEの場合、つまりスタンバイブローカーが使用できない場合、localTransactionStateはRollbackに設定され、この時点ではローカルトランザクションは実行されないことに注意してください。その後、endTransactionメソッドが第2段階の送信を担当します。ソースコードのリスト-4を参照してください。endTransactionの実装に固有:

ソースリスト-13

public void endTransaction(
    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());
    }
    String transactionId = sendResult.getTransactionId();
    final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(sendResult.getMessageQueue().getBrokerName());
    EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
    requestHeader.setTransactionId(transactionId);
    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;
    }

    requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
    requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());
    requestHeader.setMsgId(sendResult.getMsgId());
    String remark = localException != null ? ("executeLocalTransactionBranch exception: " + localException.toString()) : null;
    // 采用oneway的方式发送二阶段消息
    this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark,
        this.defaultMQProducer.getSendMsgTimeout());
}

一方向に送信される理由である第2フェーズで送信する場合、これはトランザクションメッセージに特別な信頼できるメカニズムがあるためであると理解しています。チェックバックしてください。

メッセージレビュー

ブローカーが一定の期間を経過しても、トランザクションメッセージの第2フェーズをコミットするかロールバックするかについての正確な情報をまだ取得していないことがわかった場合、ブローカーはプロデューサー(おそらくプロデューサー)に何が起こったかを知りません。がダウンしているか、プロデューサーがコミットを送信する可能性がありますが、ネットワークジッターが失われた場合は...)、私は率先してレビューを開始しました。
トランザクションメッセージのバックチェックメカニズムは、ブローカー側により多く反映されます。RocketMQのブローカーは、ハーフメッセージ、操作メッセージ、実際のメッセージの3つの異なるトピックで、さまざまな送信段階のトランザクションメッセージを分離します。これにより、コンシューマーは、コミットを配信する必要があることを最終的に確認するメッセージのみを表示できます。詳細な実装ロジックは当面この記事では繰り返されません。ブローカーの観点からそれを解釈するために別の投稿を開くことができます。

プロデューサーの視点に戻ると、ブローカーのレビューリクエストを受信すると、プロデューサーはメッセージに従ってローカルトランザクションのステータスを確認し、結果に基づいて送信またはロールバックを決定します。これには、プロデューサーがレビューの実装を指定する必要があります。緊急。
もちろん、通常の状況では、UNKNOWステータスを積極的に送信することはお勧めしません。このステータスは、間違いなくブローカーに余分なチェックバックオーバーヘッドをもたらします。予測できない異常な状況が発生した場合にのみチェックバックメカニズムを開始するのが妥当です。 。

さらに、バージョン4.7.1のトランザクションレビューは無制限のレビューではありませんが、最大15のレビューです。

ソースリスト-14

/**
 * The maximum number of times the message was checked, if exceed this value, this message will be discarded.
 */
@ImportantField
private int transactionCheckMax = 15;

付録

プロデューサーの公式のデフォルトパラメーターは次のとおりです(タイムアウト期間パラメーターについても前の記事で説明しました。デバッグ結果はデフォルトの3000ミリ秒であり、10000ミリ秒ではありません)。
RocketMQソースコードの調査[1]-プロデューサーの観点からのトランザクションニュース

RocketMQは優れたオープンソースのメッセージミドルウェアであり、多くの開発者がそれに基づいて二次開発を行っています。たとえば、Ant Group SOFAStack MQメッセージキューの商用製品は、RocketMQカーネルに基づいて再開発された金融グレードのメッセージミドルウェアです。情報の管理と制御、透過的な運用と保守において、多くの優れた作業が行われています。
RocketMQが、開発者のコ​​ミュニティの共創と構築の下で、より活力を持って成長し、発展し続けることを願っています。

おすすめ

転載: blog.51cto.com/14898876/2605860