あなたが読んRocketMQトランザクションメッセージソースの分析を行います(ドライ)

序文

インターネット業界のおかげでMQ負荷シフト、デカップリング、非同期操作およびその他の機能は、分散サービスへの場所を持っていると言うことができ、MQは、多くの場合、不在ではありません。新機能のトランザクションメッセージの4.3.0バージョンを発売しRocketMQ並行性の高い課題のより多くの経験豊富な二重の11年の研究所からのアリは、紙がリーダーで読み取り、取引メッセージ追跡レポートに関連するソースコードの4.5.0バージョンをRocketMQあなたが知っていることができます。

  • 問題のいずれかの種類を解決するために、Transactionメッセージ
  • その設計の原則は、取引メッセージを強調します

この問題を解決します

私は今、このようなシナリオがあるシステムと仮定します。

ローカルオープン引き落としトランザクションデータベース、ストックセンターへの配信成功後に送信されるMQメッセージ。

一部の人々はまだライン上で一緒にオープンMyBatisのトランザクションの実装、ローカル・トランザクションおよびMQメッセージングではないと思うかもしれ?MQが正常に送信された場合は、一度にトランザクション、全体の操作をロールバックに失敗した送信し、トランザクションをコミットします。

transaction{
  扣款();
  boolean success = 发送MQ();
	if(success){
    commit();
  }else{
    rollBack();
  }
}
复制代码

一見問題が、ないネットワークが信頼できるものではありませんインチ

応答オーバーMQの戻り仮定ネットワークは、その理由をもらっていないので、私は不確実なリターンMQ結果の顔にロールバックする必要がありましたので。しかし、MQサーバはそう失敗担当、成功した配信の結果であり、実際にこのメッセージを受信したが、失われたクライアントに応答します。

シーン

MQメッセージとローカル業務を送っているので、全体的な需要はそれの原子性を持っていることを確認する方法を、一緒に書き込むことはできませんか?:答えは、今日私たちが主人公紹介ということであるトランザクションメッセージを

概要

概要)

全体RocketMQ取引メッセージは、2つの主要なラインに分割されています

  1. 送信プロセスタイマタスク:送信半メッセージ(メッセージの半分)は、ローカル・トランザクションを実行するトランザクションの実行結果を送信します
  2. 定期的なタスクの逆流を確認してください:MQサーバーをバックローカル業務をチェックするために、トランザクションの結果を送りました

したがって、この紙はまた、ソースの二つの主要な線によって分析しました

ソースコード解析

セミメッセージ送信プロセス

ローカルアプリケーション(クライアント)

ローカルアプリケーショントランザクションメッセージがTransactionMQProducer送信されるコアクラスは、クラスコードの量に関連付けられた多数決論理によって送信された継承クラスDefaultMQProducer多重化されたメッセージが非常に小さい場合にのみ100行を、このクラスの以下の方法がsendMessageTransactionあります

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

    return this.defaultMQProducerImpl.sendMessageInTransaction(msg, null, arg);
}
复制代码

この方法は、2つのことを行い、

  1. transactionListenerがあるかどうかを確認してください
  2. トランザクションメッセージを実行するために親を呼び出し

TransactionListenerは、トランザクション・メッセージ・フローに重要な役割を果たし、このインタフェースを見てみましょう

public interface TransactionListener {
    /**
     * When send transactional prepare(half) message succeed, this method will be invoked to execute local transaction.
     *
     * @param msg Half(prepare) message
     * @param arg Custom business parameter
     * @return Transaction state
     */
    LocalTransactionState executeLocalTransaction(final Message msg, final Object arg);

    /**
     * When no response to prepare(half) message. broker will send check message to check the transaction status, and this
     * method will be invoked to get local transaction status.
     *
     * @param msg Check message
     * @return Transaction state
     */
    LocalTransactionState checkLocalTransaction(final MessageExt msg);
}
复制代码

インターフェースコメントは、ビューの上記の概要であると、executeLocalTransaction対応する方法ですることであり、それは非常に明確にローカル業務の実行 checkLocalTransaction対応であり、操作をローカル業務のチェックに戻る動作を制御します。

以下では、ソースDefaultMQProducerクラスsendMessageInTransactionの方法であって、

public TransactionSendResult sendMessageInTransaction(final Message msg,
                                                      final LocalTransactionExecuter localTransactionExecuter, final Object arg)
    throws MQClientException {
    ...
    SendResult sendResult = null;
    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
    						...
        sendResult = this.send(msg);
    						...
    switch (sendResult.getSendStatus()) {
        case SEND_OK: {
            		...
        localTransactionState = transactionListener.executeLocalTransaction(msg, arg);
                ...
        break;
        case FLUSH_DISK_TIMEOUT:
        case FLUSH_SLAVE_TIMEOUT:
        case SLAVE_NOT_AVAILABLE:
            localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
            break;
        default:
            break;
    }

    						...
        this.endTransaction(sendResult, localTransactionState, localException);
								...
}
复制代码

ソースコードより直感的なロジックを作るために、著者は、コアコードを合理化します。sendMessageInTransaction主な方法は、以下のことを行います

  1. 通常のMQメッセージサーバとトランザクションメッセージを区別するために、関連するマーカーでマークされたメッセージへのトランザクションメッセージ
  2. メッセージの半分(半分メッセージ)を送ります
  3. transactionListenerによってローカル事務の成功の実装を送ります
  4. 場合ENDTRANSACTION実行方法は、半障害メッセージが送信されたり、ローカル・トランザクションが失敗したメッセージの半分を削除するには、サーバーに伝えるために、半分のメッセージが正常に送信される、ローカルに成功総務を行って、力の半分にニュースサーバに語りました。

さんが最後RocketMQサーバーに対処する方法を見てみましょう、ここでは、クライアント側のコードはほとんど終わって、半分のメッセージ・フローを送信

RocketMQサーバー

メッセージを受信した後Serverが変換され、取引メッセージを理解するためのメッセージの権限はほとんど使用SomeDomainObjectをチェックし、トランザクションをサポートするかどうかを、ここでは、遠位の支流の導入を省略する。次はのTransactionalMessageBridge半分メッセージ処理ソースカテゴリです

public PutMessageResult putHalfMessage(MessageExtBrokerInner messageInner) {
    return store.putMessage(parseHalfMessageInner(messageInner));
}

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

これらの2つの方法は、次のことを行うために、主に以下のとおりです。

public class Message implements Serializable {
    private static final long serialVersionUID = 8445773977080406428L;

    private String topic;
    private int flag;
    private Map<String, String> properties;
    private byte[] body;
    private String transactionId;
}
复制代码
  1. 独自のマップキャッシュでのメッセージの本文にメッセージキューIDの話題
  2. 「RMQ_SYS_TRANS_OP_HALF_TOPIC」に設定されたメッセージのトピックは、キューIDが0に設定されています
  3. メッセージは、ディスクの永続性に書き込まれます

あなたは、このように半メッセージを避け、区別トピックでの話題と同じキューに置かれます半分メッセージのすべての業務を見ることができ、消費者に消費者にあります

その後、半永続的メッセージとサーバーは、私たちのローカルアプリケーションに結果を送信します。メッセージのサーバー側の処理をここにするには、タスクを時限デビューに続いて、半分以上です。

定期的なタスクの逆流をチェック

RocketMQサーバー

タスクはスレッドTransactionalMessageServiceクラスと呼ばれるタイミング、次のメソッドは、クラスを確認しています

@Override
public void check(long transactionTimeout, int transactionCheckMax,
    AbstractTransactionalMessageCheckListener listener) {
                  ...
     if (!putBackHalfMsgQueue(msgExt, i)) {
        continue;
     }
       listener.resolveHalfMsg(msgExt);
   } 
									...
}
复制代码

この方法は、非常に長いチェック、コードは実質的に半分のメッセージで省略は(例えば72時間を超えるようなトランザクションメッセージは、それが期限切れとしてカウントされる)バックその調査対象のメッセージの半分だけを残して、濾過しました。

半処理されたメッセージは、ディスク引っ張っメモリからそのプロパティを変更(例えばTRANSACTION_CHECK_TIMES、情報が破棄されたトランザクションのキーメッセージである)、そのメッセージを返送する前にチェックするたびので、非常に興味深い一つは、putBackHalfMsgQueue方法でありますあなたは再び半分のディスク上に置くメッセージを必要としています。採用RocketMQ方法は、最新の物理オフセットに基づいて書き換え、むしろ半分よりも、元のメッセージの修正目標はRocketMQストレージの設計は、あなたがメッセージを変更するために行けば、高いパフォーマンスを行うことができない、シーケンシャル書き込みを使用している中で、。

ここでresolveHalfMsgの方法があり、主にスレッドを開き、チェックメッセージを送信します。

public void resolveHalfMsg(final MessageExt msgExt) {
    executorService.execute(new Runnable() {
        @Override
        public void run() {
            try {
                sendCheckMessage(msgExt);
            } catch (Exception e) {
                LOGGER.error("Send check message error!", e);
            }
        }
    });
}
复制代码

ローカルアプリケーション(クライアント)

方法DefaultMQProducerImpl checkTransactionStateは、ローカルアプリケーション処理ロジックリコールメッセージであり、以下の

@Override
public void checkTransactionState(final String addr, final MessageExt msg,
    final CheckTransactionStateRequestHeader header) {
    Runnable request = new Runnable() {
        ...
        @Override
        public void run() {
            ...
     TransactionListener transactionListener = getCheckListener();
            ...
     localTransactionState = transactionListener.checkLocalTransaction(message);
               ...
                 
      this.processTransactionState(
                    localTransactionState,
                    group,
                    exception);        
        }
      
        private void processTransactionState(
           ...
 DefaultMQProducerImpl.this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, thisHeader, remark,
                    3000);
           ...
        }
    };
    this.checkExecutor.submit(request);
}
复制代码

はっきりと見ることができるコードのロジックを合理化した後、

  • ロジック裏調査を実行するスレッドを開き、
  • transactionListenerローカルトランザクションの実行の実装の結果を得るためにcheckLocalTransaction方法

RocketMQサーバー

RocketMQサーバはクライアントを受け取った後、私はメッセージを送っコミット

削除前>半メッセージ - - 半分のメッセージを読んで>そして再び共通メッセージとしてディスクに書き込ま - 情報>など、元のメッセージの本文として話題を復元します

ロールバックメッセージはメッセージ削除の半分である前に、それがある場合

RocketMQトランザクションメッセージのこの、呼び出しチェーン全体は終わりました

考えます

1.分散トランザクション等しい総務のニュース?

どちらも、消費者が一定の成功が保証されていない消費することができるかどうか、ローカルのみの業務とMQメッセージング原子状全体を保証し、MQサーバーへの送達後に、トランザクションメッセージングを問題ではありませんでした。

2.ソースデザインとは何浮き彫りに?

理解するための全体のリンク元学習の発見を通じて、明るいスポットがたくさんあります

  • メッセージ、メッセージ処理クライアントサイドのロジックリコールを送信するためにエンドサーバーをバックチェック、クライアント側の提出がメッセージを非同期に使用されているコミット/ロールバック、非同期非同期、非同期的な場所を持っているし、再試行すると言うことができますそれは、分散環境での全体的なロジックには影響しません、でも短いネットワークの状態が良くないことを保証します。
  • TransactionListener、真のオン・オフの原理と依存関係逆転の原則、指向プログラミング・インターフェースをご紹介します。全体のスケーラビリティは非常によくやった、ユーザーは非常に便利な、リスナーがメッセージを送信するために行うことができ、独自の事務を作成する必要があります
  • 関連したロジックに送信継承DefaultMQProducer大幅多重メッセージを通じてTransactionMQProducer

3.ソースデザインを何不足?

欠陥を見つけるために非常に成功したメッセージングミドルウェアとしてRocketMQは、そう簡単ではないですが、私はいくつかの発言を作ります

  • sendMessageIntransaction他のトランザクションに関連する方法は、凝集の観点から、内部DefaultMQProducer分割され、これは関連する方法はTransactionMQProducerを分割しなければならないメッセージトランザクションで送信されます。

  • すべての半トピックメッセージはメッセージキューRMQ_SYS_TRANS_OP_HALF_TOPICの半分のためのトピックに書かれており、それぞれの半分のメッセージは、全体の書かれた多くの回内のリンク、同時大とメッセージのほとんどの場合があるだろう、そのメッセージは、トランザクション、信頼性があります問題があるでしょう。

おすすめ

転載: juejin.im/post/5d2a6960f265da1bae39295e