RocketMQブローカーがメッセージのコミットを処理するときに、ロックでスピンロックまたは再突入ロックを使用する必要があります

議論のトピック

次のコンテンツは、rocketmq4.7.1バージョンに基づいています。

rocketmqのブローカー側でのメッセージ送信に関する構成項目には、次の2つの構成があります。

1.リエントラントロックを使用するかどうか

    /**
     * introduced since 4.0.x. Determine whether to use mutex reentrantLock when putting message.<br/>
     * By default it is set to false indicating using spin lock when putting message.
     */
    private boolean useReentrantLockWhenPutMessage = false;

デフォルト値はfalseです。スピンロックが使用されます。この構成アイテムの値は、次の構成によって異なります。

2.メッセージの送信を処理するワーカースレッドの数:

    /**
     * thread numbers for send message thread pool, since spin lock will be used by default since 4.0.x, the default
     * value is 1.
     */
    private int sendMessageThreadPoolNums = 1; //16 + Runtime.getRuntime().availableProcessors() * 4;

デフォルト値は1で、メッセージの送信を処理するスレッドプールの数です。

この記事では、実際のビジネスシナリオでスピンロックまたは再突入ロックを使用することが適切かどうかについて説明します。sendMessageThreadPoolNumsがデフォルトの1を使用するか、後でコメントする値を使用するかが適切です。

これについて話す前に、ブローカーロックが使用されている場所について説明しましょう。

ロック位置

「ロック」はプログラムのパフォーマンスに非常に敏感です。ロックがあると、プログラムの処理能力が不足しやすくなります。Rocketmqは、非常に高いスループットを備えたメッセージングエンジンであると言えます(Kafkaとは比較されません)。ロック位置はどこですか?

ブローカーは、プロデューサークライアントから送信された要求を受信すると、その要求をタスクとしてカプセル化し、処理のために送信側スレッドプールに送信します。スループットを向上させたい場合は、メッセージを処理するスレッドプール内のスレッド数最高のパフォーマンスを実現するには、適切な値を設定する必要があります。ただし、ここで設定するデフォルト値は1ですか。それで、それの理由は何ですか、見下ろしてください:

メッセージ送信要求を処理する場合、このスレッドプールにワーカースレッドがいくつあっても、最終コミットメッセージがページキャッシュに送信されると、操作できるワーカースレッドは1つだけです。これはシリアルアクションです。

ロックコードは、CommitLogクラスのputMessageメソッドにあります(メソッドがオーバーロードされ、複数の場所があり、以下は通常のメッセージ送信、非バッチを選択します)

        // 这是加锁的位置
        putMessageLock.lock(); //spin or ReentrantLock ,depending on store config
        try {
            long beginLockTimestamp = this.defaultMessageStore.getSystemClock().now();
            // 不要忽视这个字段,影响很大的
            this.beginTimeInLock = beginLockTimestamp;

            // Here settings are stored timestamp, in order to ensure an orderly
            // global
            msg.setStoreTimestamp(beginLockTimestamp);

            if (null == mappedFile || mappedFile.isFull()) {
                mappedFile = this.mappedFileQueue.getLastMappedFile(0); // Mark: NewFile may be cause noise
            }
            if (null == mappedFile) {
                log.error("create mapped file1 error, topic: " + msg.getTopic() + " clientAddr: " + msg.getBornHostString());
                beginTimeInLock = 0;
                return new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, null);
            }

            // commit message 的动作
            result = mappedFile.appendMessage(msg, this.appendMessageCallback);

そのスピンロックと再突入ロックを見てください。

CAS実装に基づくスピンロック:

public class PutMessageSpinLock implements PutMessageLock {
    //true: Can lock, false : in lock.
    private AtomicBoolean putMessageSpinLock = new AtomicBoolean(true);

    @Override
    public void lock() {
        boolean flag;
        do {
            flag = this.putMessageSpinLock.compareAndSet(true, false);
        }
        while (!flag);
    }

    @Override
    public void unlock() {
        this.putMessageSpinLock.compareAndSet(false, true);
    }
}

Java再入力ロックを使用した再入力ロック:

public class PutMessageReentrantLock implements PutMessageLock {
    private ReentrantLock putMessageNormalLock = new ReentrantLock(); // NonfairSync

    @Override
    public void lock() {
        putMessageNormalLock.lock();
    }

    @Override
    public void unlock() {
        putMessageNormalLock.unlock();
    }
}

明らかに、前者は楽観的ロックであり、後者は悲観的相互排除ロックです。スレッド数が3未満の場合は、スピンロックを使用する方がよいと思います。スレッドが多すぎる場合は、リエントラントロックを構成して有効にし、多くのスレッドがアイドリングしてCPUを浪費しないようにします。

現在、これらは一般的にマルチコアプロセッサです。メッセージを送信するワーカースレッドの数が1の場合、スループットはマルチスレッド構成よりも低くなるはずです。以前は、クラウドサーバー、仮想マシン、4C8G構成でストレステストを行っていましたが、sendMessageThreadPoolNumsをデフォルト値の1に設定し、メッセージ本文を1KBに送信すると、単一のブローカーのTPSはわずか1000になります。再入可能ロックを設定します。スレッド数を設定すると、TPSが増加します。64-128スレッドを使用すると、1W5を超えて2Wに近づきます。もちろん、このパフォーマンスの変曲点で最適なスレッド数をテストしませんでした。サーバー上。

デフォルト構成のsendMessageThreadPoolNums値1の重要性は何ですか?

sendMessageThreadPoolNumsが1でない場合、何が問題になる可能性がありますか

sendMessageThreadPoolNumsが1の場合、メッセージがブローカーに送信された後、メッセージが処理され、コミットされてから、すべて整然とフラッシュされることが保証されます。ブローカーに最初に送信されるメッセージは、最初に送信されるメッセージでもあります。書かれています。

複数のワーカースレッドがあり、コミットするとき、それはシリアルであり、1つのスレッドのみが書き込むことができ、要求を処理する他のワーカースレッドがブロックされ、ロック(非フェアロック)の取得を待機している場合、ブローカーはどちらを保証しません。スレッドがロックを取得すると、これらのブロックされたスレッドがロックをめぐって競合した後、後で送信されるメッセージが最初に書き込まれる可能性があります。このように、ブローカーが受信するメッセージのシーケンスが書き込みのシーケンスと一致しているという保証はありません。

異なるシーン構成

1.分散メッセージの順序を確認する場合は、デフォルトの構成であるスピンロックを使用します。メッセージ処理スレッドの数は1です。

2.他のシナリオでは、メッセージ送信のスループットにもっと注意を払い、tpsが表示されない場合(構成に関連している可能性があります)、再入力ロックを有効にして、に従って圧力テストを実行します。マシン構成と実際のシナリオを設定し、1つを設定します。メッセージ送信処理スレッドプール内の最適なスレッド数(テストするのが面倒な場合は、コメント値の4倍+16 CPUを参照してください)。メッセージの順序に影響しますか?分散シナリオでメッセージの順序を確認したい場合は、最初の項目を参照してください。そうでない場合は、現在のクライアントの業務でメッセージの順序を確認する必要がある限り、 、順番に並べる必要があります。、シーケンシャル送信は同期ブロッキングインターフェイスであるため。現在の業務では、最初のメッセージが返信応答に書き込まれない場合、次のメッセージは確実に送信されないため、必要ありません。この問題を心配するのは、この業務のメッセージが「順序付き」である限り、書き込みで十分です。ブローカー側で他のメッセージを送受信する順序が一貫しているかどうかは問題ではありません。

3.分散シナリオでメッセージを注文する必要があるだけでなく、TPSを十分に高く送信する必要がある場合、水平方向に拡張してさらにいくつかのノードをデプロイすることしかできません。

これら2つの構成項目について他にご意見がございましたら、メッセージを残してご相談ください。

フォローアップの補足として、デフォルト構成で、メッセージ本文が1KBのみで、消費なしで送信された場合、圧力テスト中にtpsは50,000に達しました。上記の理由は、このシナリオではメッセージ本文が少し大きいためです。 。メッセージ本文が比較的小さい場合は、デフォルト設定で問題ありません。DLedgerモデル、同期フラッシュ、メッセージ本文が非常に大きい場合など、tpsをアップロードできない場合は、上記の提案を参照して、数を増やしてください。スレッドの数を増やし、リエントラントロックを使用してみてください。

おすすめ

転載: blog.csdn.net/x763795151/article/details/111147255