RocketMQ 5.X PopAck ソース コードの逆アセンブリ

目次

1. RocketMQ 5.X アーキテクチャ

  1. RocketMQ 5.X アーキテクチャ
  2. RocketMQ 5.X Pop が発明された理由

2.ポップ処理

  1. ポップ処理
  • 都コンシューマキュー
  • ポップオフセットの計算
  • メッセージを読む
  • チェックポイントの追加
  • コンシューマキューのロックを解除する
  1. ポップキーのデータ構造の概要
  • ポップオフセット
  • チェックポイント
  • レシートハンドル
  • 開始オフセット情報
  • MsgOffsetInfo
  • 注文数情報

3. 確認応答処理

  • プロキシが Ack リクエストを送信する
  • メモリマーク消費進捗状況
  • トピックを復活させるために Ack を維持する
  • 非同期マーク消費の進行状況
  • 表示された時間が経過すると、メッセージの消費が再開されます。

4. 結論

RocketMQ 5.X アーキテクチャ

RocketMQ は 5.X 時代を開始し、4.X が LTS バージョンになりました。

大手クラウドベンダーからも RocketMQ 5.X バージョンに対応した製品が発売されており、Pop と Ack を導入する前に RocketMQ 5.X のアーキテクチャを理解する必要があります。

1. RocketMQ 5.X アーキテクチャ

上に示したように、RocketMQ 5.X アーキテクチャでは、新しいコンポーネントは次のとおりです。

  • 02 コントローラー: ブローカーがマスターとスレーブを切り替えるのを支援するコントローラー。

  • 04 プロキシ: RocketMQ のプロキシ サービスは、gRPC プロトコル クライアントとリモート クライアントによるメッセージの送受信をサポートします。

  • 05 gRPC クライアント: gRpc プロトコルを使用して RocketMQ プロキシにアクセスする RocketMQ 5.X の新しいクライアント。

注: コミュニティ内の多くの友人が、このクライアントが 4.X クラスターにアクセスできるかどうかを尋ねましたが、その答えはサポートされていないというものでした。

5.X には実際には 2 つのクライアントがあり、最初に gRPC クライアントが推奨されます。

5.X gRPC クライアント ソース コード: https://github.com/apache/rocketmq-clients

5.X リモーティング クライアントのソース コード:

https://github.com/apache/rocketmq/tree/develop/client

5.X gRPC クライアントは gRPC プロトコルを使用してプロキシにアクセスし、5.X Remoting クライアントは Remoting プロトコルを使用して Namesrv およびプロキシにアクセスできます。

残りのコンポーネントは RocketMQ 4.X のネイティブです。 5.X と 4.X の違いの詳細については、「RocketMQ 5.0 と 4.9」を参照してください。

2. RocketMQ 5.X が Pop を発明した理由

Pop は主に、プッシュおよびプルの消費者が直面する 4 つの一般的な問題を解決します。

  • 消費者が立ち往生している問題

以下の図は、Push コンシューマが Consumer Queue にサブスクライブする場合の状況を示していますが、GC などにより Push Client 2 の実行が極端に遅くなると、Broker 1-1 の Queue 1 と Broker 2-1 の Queue 1 が溜まってしまいます。

次の図は、Pop コンシューマが Consumer Queue にサブスクライブするときの状況を示しています。

上の図からわかるように、各 Pop クライアントはすべてのブローカーのすべてのコンシューマ キューを消費します。
Pop Client2 がスタックすると、他の Pop Client がすべての Consumer Queue を消費します。Push 消費では、スタック消費または誰も消費しないことによるキューの蓄積の問題が解決されます。

  • 負荷分散が遅いという問題

Push Consumer がスタックした場合、または GC によって消費が遅くなった場合、通常は Consumer プログラムを再起動することで一時的に問題を解決します。

リバランスはコンシューマの再起動後に実行されます。コンシューマの数が多いほどリバランスに時間がかかり、リバランス中にコンシューマはメッセージを消費できません。

Pop が消費する場合、消費者のオンラインまたはオフラインは Reblance をトリガーしないため、負荷分散が遅いという問題はありません。

  • ステートフルからステートレスへの変更

ポップは無国籍の消費者です。クラウドネイティブ環境では、ステートレス サービスの方が便利で、拡張/縮小も高速です。

  • 消費インスタンス数の上限

Push コンシューマの最大数は、コンシューマ キューの数を超えることはできません。Pop にはこの制限はなくなりましたが、独自の制限もあります。

Broker は、Pop 中に Lock Consumer Queue を通じて Pop メッセージを実装しますが、複数の Pop コンシューマ クライアントがロックを競合する時間が発生し、Pop コンシューマの数が次々と増加するため、Pop コンシューマの数は無限に増加することはできません。

次にConsumer Queueをロックする方法について説明します。

ポップ処理

Broker では、Pop の実装コードは次から始まります。

PopMessageProcessor.processRequest(ChannelHandlerContext, RemotingCommand) メソッドによって開始されます。

1.ポップ処理

著者は Pop メッセージを 5 つのプロセスに分割しますが、ここではデータ検証と Pop 前のパラメータ準備は無視されます。

ここではコードを直接見ることができます。PopMessageProcessor.processRequest() は PopMsgFromQueue() メソッドを呼び出します。このメソッドは Pop メッセージの実装の鍵です。作成者はそれを 5 つのステップに分けています:

1、锁 コンシューマ キュー

メッセージをポップするときは、QueueLockManager.tryLock(lockKey) メソッドを呼び出してロックを実装します。ロックキーの形式は次のとおりです。

String lockKey =
            topic + // topic名字            
            PopAckConstants.SPLIT +  // 分隔符            
            requestHeader.getConsumerGroup() + // 消费者组            
            PopAckConstants.SPLIT +  // 分隔符            
            queueId; // consumer queue id

Consumer Queue は、同時に同じコンシューマ グループ内の 1 つのコンシューマ インスタンスによってのみロックされることがわかります。

同じコンシューマ グループ内にメッセージを同時にポップするコンシューマ インスタンスが 2 つある場合、正常にロックされるのは 1 つだけです。

このロックの実装コードは次のとおりです。

このロックについては次の 2 つの点に注意してください。

  • これはタイムロックです。ロックにはタイムアウト期間があり、ロック時間が経過すると自動的に解放されます。図のマーク 1 からわかるように、現在のロック サービスは ServiceThread ですが、RocketMQ では、これはサービスがバックグラウンド スレッドであり、自動的にチェックを実行することを意味します。

  • 効率的なロック。ロックは ConcurrentHashMap のスレッド セーフによって実装されており、Java の一般的な面接でよく聞かれることだと思います。

2. ポップオフセットを計算する

Pop Offset は、このコンシューマ キューのどのオフセットが現在メッセージのプルを開始する必要があるかを示します。

Pop メッセージ フローには、Pop Offset が計算される場所が 2 か所あります。

  • 第一位。 QueueXX をロックする前にポップ オフセットを計算します。ロックが失敗した場合、ポップ オフセット  を使用して、このコンシューマ キュー内に消費されていないメッセージがどれだけあるかを 推定します。

つまり、戻りフィールドは RestNum です。

  • 2位。 QueueXX を正常にロックした後、ポップ オフセットを計算します。

なぜ再計算する必要があるのでしょうか?ポップ オフセット計算の最初の実行からロックが成功するまでの期間に、他の人が消費位置を更新した可能性があり、最初に計算されたポップ オフセットが不正確になります。QueueXX が正常にロックされた後、QueueXX は次のユーザーによってのみ使用されます。現在の顧客のターミナル ポップ メッセージでは、この時点でポップ オフセットの値が正確な値になるように再計算され、ストアに移動してこの値に基づいてメッセージを読み取ります。

3. メッセージを読む

メッセージを読み取るには、 this.brokerController.getMessageStore().getMessageAsync() メソッドを呼び出して読み取ります。これについては、次回詳しく説明します。

4. チェックポイントを追加する

各Popのメッセージ情報を記録したCheck Pointメッセージ(略してCKメッセージ)。

メッセージを読み取ると、チェックポイントメッセージ (略して CK メッセージ) が生成されます。 CK メッセージはバッファに書き込まれます。呼び出しコードは次のとおりです。

これらのメッセージはバッファに書き込まれた後、不可視の時間に入ります。つまり、同じコンシューマ グループの他のコンシューマ インスタンスはメッセージを読み取ることができなくなります。

バッファに書き込んだ後、他のコンシューマ インスタンスがポップ オフセットを計算するときに、ポップ メッセージがバッファに含まれるため、メッセージは読み取られません。

非表示の時間が経過し、ユーザーが Ack を取得していない場合、これらの Pop メッセージは Revive サービスによってユーザーのトピックに復元され、ユーザーによって消費されます。

5. Consumer キューのロックを解除します。

これについては何も言うことはありません。ロックされた画像を見てください。

2. Pop の主要なデータ構造の紹介

  • ポップオフセット

ポップ オフセットは、各コンシューマ インスタンスがメッセージをポップするときに計算され、ポップのキューで消費できるメッセージの開始点になります。

RocketMQ は、このポップ オフセットを使用してストレージからメッセージを読み取ります。

メッセージを読み取るプロセスは 4.X と似ているため、ここでは詳しく説明しません。

以下の図は、ポップ オフセットの計算方法を示しています。

ポップ オフセット値の計算には 3 つのソースがあります。
ステップ 1: 送信されたサイトをクエリします。ユーザーが消費を完了し、消費場所を送信するたびに、ここで更新されます。

ステップ 2: リセットされた消費場所を確認します。現在のバージョン 5.1.4 のリセット消費ポイントも別途保存されます。これは 5 の新しいロジックです。

ステップ 3: Ack によって送信された消費サイトを確認します。 Pop は通常、メッセージのバッチをポップし、Ack は 1 つずつ Ack される可能性があるため、現在の Ack がどのメッセージに送信されているかを確認する必要があります。ポップされたが確認されていないメッセージは、再試行されるか、再試行されるまで再度ポップすることはできません。ユーザートピック。

  • チェックポイント

データ構造は次のとおりです。

public class PopCheckPoint implements Comparable<PopCheckPoint> {
// 本次pop消息d的起始consumer queue offset
private long startOffset;  
    // 本次pop时的时间戳,单位毫秒
private long popTime;
// 本次pop消息d的不可见时间,单位毫秒    
     // 一般来自pop客户端请求的request header
private long invisibleTime;    
    // 特别重要    
    // 记录本次pop消息的ack情况
private int bitMap;
// 本次pop消息d的条数
private byte num;
// 本次pop的consumer queue id
private int queueId;    
    // 本次pop 的topic
private String topic;    
    // 本次pop 的消费者组
private String cid;
// 特别重要    
     // revieve topic的位点,后面详细讲解
private long reviveOffset;
// 特别重要    
     // 本次拉取消息d的每个消息的queue offset 减去 pop offset    
     // 的差值
private List<Integer> queueOffsetDiff;
// 本次pop 消息所在d的broker
private String brokerName;}

特に重要なフィールドのいくつかについては、以下で詳しく説明します。

  • ビットマップ

このフィールドは int 型です。1 つの Int は 32 ビットで表されます。各ビットは実際には 0、1 です。RocketMQ は BitMap を使用して、この Pop のどのメッセージが Ack であるか (1 としてマーク)、どのメッセージが Ack ではないか (1 としてマーク) をマークします。 .0)。具体的な処理の詳細については、後述するAckの処理を参照してください。

  • ReviveOffset

Revive の英語訳は回復を意味します。これらの目に見えないメッセージの基本情報 (非メッセージ本文) は Revive トピックに保存されます。時間が経過すると、ユーザーは Revive サービスによって元のトピックに復元されます。また消費する。 ReviveOffset は、この Revive トピックのコンシューマ キューの位置です。

  • キューオフセット差分

QueueOffsetDiff は、この Pop 内の各メッセージの消費位置と Pop Offset の差を保存する配列です。これは、RocketMQ による Ack の実装を支援するために使用されます。
さらに詳しく知りたい場合は、このフィールドがソース コードでどのように使用されているかを読むことができます。

  • レシートハンドル

この値は、メッセージごとに 1 つずつ、メッセージのハンドルと呼ばれます。この値は、Ack が発生するとブローカーに渡されます。ブローカーは、解析を通じてどの Ack 時刻とどの Pop メッセージを決定します。形式は次のとおりです:

上の図から、いわゆるハンドルが実際にはメッセージの一連の属性によって結合された文字列であることがわかります。

実際の文字列は次のようになります。

  • 開始オフセット情報

この値は Pop であり、一度に 1 つの値を記録し、Pop の開始サイト情報を記録します。実際の形式は次のとおりです。

このデータ構造は主に、Pop メッセージのハンドルである Pop_ck の構築を支援するために Proxy で使用されます。シンプルなデータなので、サンプルを自分でデバッグして確認することができます。プロキシで使用されるコードは次のとおりです。

上記はハンドル Broker が構築されたことを意味するものではありませんが、なぜ Proxy を再度構築する必要があるのでしょうか?誰もがそれについて考えることができます。

  • MsgOffsetInfo

この値は Pop であり、一度に 1 つの値を記録し、Pop の各メッセージの位置情報を記録します。実際の形式は次のとおりです。

このデータ構造は主に、Pop メッセージのハンドルである Pop_ck の構築を支援するために Proxy で使用されます。シンプルなデータなので、サンプルを自分でデバッグして確認することができます。プロキシで使用されるコードは次のとおりです。

  • 注文数情報

この値は、一度に 1 つの値をポップし、ポップ シーケンス メッセージの各メッセージの再消費時間を記録します。形式は次のとおりです。

プロキシで使用されるコードは次のとおりです。

上記のコア データ構造を通じて、Broker が Proxy で使用される Pop 用の多くのデータ構造を出力することがわかります。

著者はここで質問もあります。これらのデータ構造により、プロキシとブローカーの間の結合ロジックが増加し、プロキシが純粋にステートレスになることが困難になります。

論理結合を行わずにインターフェース結合のみを実現することは可能ですか?

確認応答プロセス

Ack は Pop 用です。Pop は複数のメッセージをポップアウトできますが、Ack は次の状況を解決する必要があります。

  • ユーザーは一度に 1 つのメッセージに確認応答します。
  • ユーザーは毎回、一連のメッセージに確認応答します。
  • ユーザーは間違いを犯しましたが、Ack はありませんでした。

上記 3 つの状況に対して 4 つの結果があります。

  • ユーザーの確認応答により、このポップのすべてのメッセージが完了しました。
  • ユーザーの確認応答がメッセージの一部を完了しましたが、確認応答の位置に穴があります。
  • ユーザーの確認応答はメッセージの一部を完了し、確認応答の位置に穴はありません。
  • ユーザーはメッセージに確認応答をしませんでした。

上記のシナリオに基づいて、RocketMQ Ack はどのように実装されますか?

著者は Ack プロセスを次のように要約しています。

Broker では、Ack のエントリ ポイントは AckMessageProcessor.processRequest() メソッドです。点線は非同期プロセス、実線は同期プロセスです。著者は以下の5つのステップに分けています。

ステップ 1: プロキシが Ack リクエストを送信する

ユーザーが Ack リクエストを送信すると、ブローカーの AckMessageProcessor.processRequest(Channel, RemotingCommand, boolean) メソッドによって処理され、AckMessageRequestHeader が解析されます。

AckMessageRequestHeader には、単一メッセージ Ack とバッチ メッセージ Ack を論理的に区別する Pop ck 情報が含まれています。

タグ 1: 単一メッセージの確認応答。

マーク 2: バッチメッセージ Ack。

マーク 3: AppendAck() メソッドは Ack のコア ロジックであり、後続のすべてのロジックはこのメソッドに実装されます。

マーク 4: AppendAck() メソッドをバッチで実行します。

マーク 4 は、非アトミック操作を扱う場合のリスクであることがわかります。バッチ送信の結果は不明ですが、最終結果は一貫しています。

ステップ 2: メモリマーク消費の進行状況

最初のステップの後、コア ロジックが AppendAck() にあることがわかります。RocketMQ は Ack リクエスト ヘッダーを AckMsg に解析し、PopBufferMergeService.addAk() を呼び出して AckMsg を PopBufferMergeService のキャッシュに書き込みます。

PopBufferMergeService は、その名前が示すように、メモリ内でのマージを提供するサービスです。

何をマージしますか? Ack メッセージと CK メッセージをマージします。つまり、Ack の Consumer Queue Offset を使用して CK 内のビットマップをマークします。

実際、これは CK 内のどのメッセージが確認応答されたかをマークすること、つまり、消費の進行状況をマークすることです。

以下では、いくつかの重要な変数について説明します:
ポイント: 現在の Ack に対応する Pop Check Point オブジェクトです。その中には、各メッセージが Ack されたかどうかをマークする BitMap があります。 .
具体的にマークする方法:

  • 4 つのメッセージが取得されて配列が形成され、各「メッセージ添え字」が 0、1、2、3 であると仮定します。
  • 4 つのメッセージ消費フラグは、配列を形成する 4 つの「バイナリ フラグ」で構成されます。
  • バイナリタグ配列は「10 進数 int」に変換して ck オブジェクトに保存できます。

回路図は以下の通りです:

Pop Check Point オブジェクトの初期化から、BitMap が Int であり、初期値が 0 であることがわかります。 0 を 2 進数に変換すると、すべての Bit が 0 であることがわかります。

このビットマップの最初の 4 ビットを例として使用して、各メッセージが Ack かどうかをマークする方法を説明します。

Int を Bit 配列である BitMap に変換します。各配列要素の添え字は、Pop メッセージの添え字を表します。
たとえば、4 つのメッセージがポップされた場合、Consumer Queue Offset の昇順で 4 つの Consumer Queue Offset 添字が存在します。

時刻 t1 に 4 つのメッセージがポップされた場合、コンシューマ キュー オフセットは [100, 101, 102, 103] です。
最初の Ack が 100 の場合、ビットマップ内の添字 = 0 のビットは 1 に設定されます。

Bit 配列の結果は、上の図の最初の列です。

初めて 101 が確認された場合、BitMap 内の添字 = 1 のビットは 1 に設定されます。
Bit 配列の結果は、上の図の 2 列目です。

最初と 3 番目のメッセージがそれぞれ Acked された場合、BitMap の結果は上図の右端の列のようになります。

各 Ack の後、BitMap を Int に変換でき、この Int は Pop Check に保存されます。
ここには 3 つの質問があります

  • すべてのメッセージは Ack です。
  • ユーザーは、許可された時間内にすべてのメッセージを完了しませんでした。
  • ユーザーが確認すると、チェック ポイント メッセージは存在しなくなります。

これらの問題については、次のステップで扱います。

ステップ 3: トピックを復活させるために承認を維持する

前のステップで、すべてのメッセージが肯定応答された場合 (これは正常です)、最終消費ポイントが Consumer Offset Manager に送信され、Consumer Offset Manager は消費ポイントを定期的に自動的に保持します。

ユーザーが許可された時間内にすべての Ack メッセージを完了しなかった場合、ポップ チェック ポイントは削除され、ユーザーはこれらのメッセージをポップし続けることができます。

このタイムアウトはポップ時間と非表示時間から計算される方法を次に示します。これは、非表示時間が経過した後にメッセージが再びポップされる理由を説明できます。

ユーザーが確認したときにポップ チェック ポイント メッセージが存在しない場合はどうなりますか?
まず、なぜ Pop Check Point が存在しないのでしょうか?

  • メモリはすべての CK を保持できません。ポップ チェック ポイント情報はメモリに保存されます。すべてのポップ チェック ポイントをここに保存することはできません。ブローカは、メモリに保存できるポップ チェック ポイントの最大数の構成 PopCkMaxBufferSize を提供します。デフォルトは 20w です。

超過すると、ポップ チェック ポイント メッセージは Revieve トピックに直接保持されます。

  • 許可された時間内に Ack を持たない CK は破棄する必要があり、この CK に対応するすべてのメッセージが再びユーザーに表示されるようになります。

チェック ポイントが存在しない場合、Ack メッセージは Revieve トピックに保存されるため、永続的な Pop Check Point と再度照合して、どのメッセージが Ack されたかをマークできます。

ステップ 4: 消費の進行状況を非同期的にマークする

前のステップの後、一部のチェック ポイント情報と Ack 情報が Revieve Topic に保持されることがわかります。

PopBufferMergeService サービスは、Revieve Topic 内の Ack および CK 情報を消費し、非同期照合を実行して、CK 情報内のどのユーザー メッセージが Ack されたかをマークするバックグラウンド サービスです。

ここには詳細がたくさんあります。デバッグして確認することをお勧めします。ここでさらに詳細が必要な場合は、メッセージを残してください。別の問題を発行します。

スキャン後、CK 内のどのユーザー メッセージがすべて確認済みかを知ることができ、消費ポイントが Consumer Queue Offset Manager に送信されます。

このステップの後、完全に確認されていない CK がまだ残っている場合はどうなりますか?次のステップを参照してください。

ステップ 5: 表示された時間が経過すると、メッセージの消費が再開されます

前のステップの後、Ack と完全に一致しない CK がまだある場合は、これらの CK に対応するユーザー メッセージが再び表示され、ユーザーは再度ポップできます。

このプロセスは、PopReviveService サービスに実装されます。これは、どの CK が完全に確認されていないかを定期的にチェックし、CK に基づいてこの CK に含まれるすべてのメッセージを再試行トピックに復元するバックグラウンド サービスでもあります。

結論

PopBufferMergeService にも多くの詳細が含まれています。各キーポイントでログを記録し、本番環境と消費環境でいくつかの Ack 状況をシミュレートし、ログ出力を確認することをお勧めします。コードと組み合わせると、すぐに理解できるようになります。さらに詳しく。
最後に 2 つの質問もあります。どなたでもご議論いただけます。

  1. 同じ Pop CK が複数回確認された場合はどうなりますか? ブローカーはそれをどのように処理しますか?
  2. Pop がメッセージを読み取れない場合、CK 情報を書き込む必要がありますか?理由は何ですか?
  3. 次回は、プロキシやタイムホイールに基づくスケジュールされたメッセージについてお話しする予定です。何か見たいことがあれば、メッセージを残してください。
SenseTime 創設者、Tang Xiaoou 氏が 55 歳で死去 2023 年、PHP は停滞 Wi-Fi 7 が完全に利用可能になる2024 年初頭にデビュー、Wi-Fi 6 の 5 倍高速 Hongmeng システムが独立しつつあり、多くの大学が「Hongmeng クラス」を設立 Zhihui Jun の新興企業が借り換え、金額は 6 億元を超え、事前評価額は 35 億元 Quark Browser PC 版が内部テストを開始 AI コード アシスタントは人気があり、プログラミング言語のランキングはすべてです できることは何もありません Mate 60 Pro の 5G モデムと無線周波数技術ははるかに先を行っています MariaDB が SkySQL を分割し、確立されました独立した企業として<​​/span> Xiaomi、Yu Chengdong 氏の Huawei からの「キールピボット」盗作声明に対応
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4587289/blog/10321887