RocketMQ の高度な使用法
1. 配車の事例分析から始めましょう
1.経営分析
1. 配車スケジュール分析チャート
ユーザーが配車サービスから配車サービスにタクシーをリクエストすると、メッセージはまず RocketMQ に順次にスローされ、その後、消費されたトランザクションは投入された順序で厳密に消費されます。ユーザーは最初に RocketMQ Sequential メッセージからプッシュされたメッセージを取得し、それを保持し、ポーリングを開始して Redis のリストに車両があるかどうかを確認します。状況は 2 つあります。
- 車両が牽引されていない: 車両が牽引されていない場合、一定時間遅延が発生し、牽引が継続されます。車両を牽引できない場合は、スピンが継続されます。車両が牽引されるまでスピンは終了しません。が得られます。
- 車両に牽引する: 車両を牽引すると、ユーザーは取得した車両に拘束され、注文などのその後の操作が開始されます。
2. ドライバーは注文を自動的に受け取ります。
ドライバーがオンラインになり、注文の自動受け取りを開始すると、トピックのプロセスは次のようになります。
- 車両のステータスは、最初は Ready ステータスに設定されます。
- 車両がユーザーを受け取ると、走行状態に設定されます。
- ユーザーが車両から降りた後も、車両は引き続き準備完了状態に設定され、車両はリストに追加されます。
3. ユーザーが車から降りる
ユーザーが降車するためにクリックした場合、主なプロセスは次のとおりです。
- ユーザーステータスは最初に停止ステータスに設定されます。
- すると、車両とユーザーとの間の束縛が解除される。
- その後、車両はリストの最後にプッシュされ、他のユーザーが車両情報を取得できるようになります。
4. ユーザーはタクシーに乗ります。
ユーザーがタクシーに乗った後のプロセスは次のとおりです。
- ユーザーのステータスを確認し、連続メッセージを RabbitMQ に送信します。
- コンシューマーはユーザー情報を取得し、車両情報をプルするためのポーリングを開始します。プルが休止状態に失敗した場合は、プルに達するまでプルを続けます。
- 牽引後にタイムアウトしたかどうかを確認し、タイムアウトした場合は直接タクシーを停止し、そうでない場合はRabbitMQタイムアウト検出キーを削除し、無効なタイムアウトを通知します
- ユーザーのステータスを「実行中」に設定すると、ドライバーが自動的に注文を受け付けます。
2. テクニカル分析
1.RocketMQ シーケンシャルメッセージ
タクシーは列に並ぶ必要があり、前の人が消費できるようにする必要があり、注文が台無しにならないようにする必要があるため、RocketMQ の連続メッセージを使用する必要があります。
2.Redisポーリングキュー
車両をキューに入れたいと考えています。MQ から車両を取得した後、キューから別の車両を取得する必要があります。取得できない場合は、車両を取得するまでポーリングを続ける必要があります。タクシーに乗る場合車両はキューに戻され、他のユーザーがタクシーに乗車できるようになり、車両は再利用されます。
2. 連続メッセージ
1.シーケンシャルタイプ
1.順序なしメッセージ: 順序なしメッセージは通常のメッセージも指します。プロデューサーはメッセージの送信のみ、コンシューマーはメッセージの受信のみです。メッセージ間の順序は保証されません。例:
- プロデューサーは、orderId 1、2、3 のメッセージを順番に送信します。
- Consumer が受信するメッセージの順序は、1、2、3、または 2、1、3 などの通常のメッセージです。
2.グローバル順序: 指定されたトピックについて、すべてのメッセージは厳密な先入れ先出し (FIFO) 順序で発行および消費されます。
たとえば、プロデューサーが orderId 1,3,2 のメッセージを送信した場合、コンシューマーも 1,3,2 の順序で消費する必要があります。
3.部分順序: 実際の開発シナリオによっては、メッセージを完全に先入れ先出しにする必要はありませんが、一部のメッセージは先入れ先出しでなければなりません。
ケース: タクシーの乗車に北京、上海、広州、深センという異なる地域が含まれるのと同じように、他の注文について心配する必要はありません。同じ地域の注文 ID がこの注文を保証できることだけを確認する必要があります。
2.ロケットシーケンスメッセージ
RocketMQ はメッセージの順序を厳密に保証できますが、この順序はグローバルな順序ではなく、パーティション (キュー) の順序であり、グローバルな順序には 1 つのパーティションしか存在できません。
1. 次の質問をします。
- 生成されたメッセージは最終的に キュー に格納されることがわかっています。トピックが 4 つのキューに関連付けられている場合、メッセージをどのキューに入れるかを指定しないと、デフォルトではメッセージが 4 つのキューに均等に分散されます。
- たとえば、10 個のメッセージがある場合、これら 10 個のメッセージはこれら 4 つのキューに均等に分散され、約 2 個が各キューに配置されます。ここで 1 つの重要な点があります。同じキューに格納されるメッセージは先入れに基づいています。先出し原則
上記のシナリオが連続しているように見えない理由は、メッセージを送信するときに、デフォルトでメッセージがポーリング方式で別のキューに送信されるためです。
2.解決策
異なる地域で異なるキューを使用することを許可しており、同じ地域からの注文が同じキューに入れられる限り、消費者は先入れ先出しが保証されます。
これにより、ローカル順序が保証されます。つまり、同じ順序が同じキューに順番に配置されるため、メッセージを取得するときに、先入れ先出しが保証されます。
3. もう一度質問します (クラスターの順序を確認する方法)。
- ここにも非常に重要な点があります。コンシューマ クラスタの場合、コンシューマ 1 はまずメッセージを取得するためにキューに行き、北京の注文 1 を取得します。それを取得した後、コンシューマ 2 は北京を取得するためにキューに行きます。注文2。
- 受け取る順序に問題はありませんが、重要なのは、最初に入手することが最初に消費することを意味するわけではないということです。消費者 1 が最初に北京の注文 1 を取得しますが、ネットワークやその他の理由により、消費者 2 はそれを最初に消費します。実際にそうします。メッセージ、これは問題を引き起こします
4. 解決策 (分散ロック):
Rocker はセグメント化されたロックを使用します。これは、Broker 全体をロックするのではなく、ロック内の 1 つのキューをロックします。これは、1 つのキューがロックされている限り、ローカルの順次消費が保証されるためです。
したがって、最終消費者側のロジックは次のようになります。
- Consumer 1 は北京注文 1 を取得するためにキューに移動し、キュー全体をロックします。ロックは、消費を完了して成功を返した後にのみ解放されます。
- 次に、次の消費者が北京注文 2 を取得しに行き、現在のキューもロックします。このようなプロセスにより、同じキューを順番に取り出すだけでなく、本当の意味で順番に消費できることが確実になります。
5. メッセージタイプの比較
グローバル順序とパーティション順序の比較
トピックメッセージの種類 | トランザクションメッセージのサポート | スケジュールされた/遅延されたメッセージのサポート | パフォーマンス |
---|---|---|---|
順序が乱れたメッセージ (通常、トランザクション、時間指定/遅延) | はい | はい | 最高 |
パーティション順序メッセージ | いいえ | いいえ | 高い |
グローバルシーケンスメッセージ | いいえ | いいえ | 一般的に |
送信方法の比較
トピックメッセージの種類 | 信頼性の高い同期送信をサポート | 信頼性の高い非同期送信をサポート | 一方向送信をサポート |
---|---|---|---|
順序が乱れたメッセージ (通常、トランザクション、時間指定/遅延) | はい | はい | はい |
パーティション順序メッセージ | はい | いいえ | いいえ |
グローバルシーケンスメッセージ | はい | いいえ | いいえ |
6. 注意事項
- 連続メッセージは現在ブロードキャスト モードをサポートしていません。
- 連続メッセージは非同期送信をサポートしていません。そうでない場合、順序は厳密に保証されません。
- 同じグループ ID は 1 種類のトピックのみに対応することをお勧めします。
- グローバル順次メッセージの場合、順序を確保するために、トピックごとに 1 つのキューのみが存在します。
3. コード例
1. プロデューサーの基本コード
public class OrderProducer {
public static void main(String[] args) throws Exception {
// 创建一个消息生产者,并设置一个消息生产者组
DefaultMQProducer producer = new DefaultMQProducer();
// 指定 NameServer 地址
producer.setNamesrvAddr("192.168.44.128:9876;192.168.44.128:9877");
// 指定组名
producer.setProducerGroup("order_producer_group");
// 初始化 Producer,整个应用生命周期内只需要初始化一次
producer.start();
for (int i = 0; i < 10; i++) {
// 创建一条消息对象,指定其主题、标签和消息内容
Message msg = new Message();
msg.setTopic("rocket_order_test_1");
msg.setTags("order_tag" + i);
msg.setBody(("第"+ i + "条消息").getBytes(RemotingHelper.DEFAULT_CHARSET));
//发送消息并返回结果
SendResult sendResult = producer.send(msg);
System.out.printf("%s%n", sendResult);
}
// 一旦生产者实例不再被使用则将其关闭,包括清理资源,关闭网络连接等
producer.shutdown();
}
}
上記のコードは、順序が正しくないメッセージを送信します。現時点では、コードの 18 行目を変更できます。
SendResult sendResult = producer.send(msg,new SelectMessageQueueByHash(),i % 4);
3. 次のように send() メソッドのソース コードをトレースします。
4. コードの実行結果は次のとおりです。
この時点で、同じキューが同じブローカーに配置されていることがわかります。
5.new SelectMessageQueueByHash()
同じキューを同じブローカーに配置できるのはなぜですか? ソースコードを再度追跡する
カスタマイズする必要がある場合は、
MessageQueueSelector
インターフェイスを直接実装できます。
7. 消費者コード:
public class OrderConsumer {
public static void main(String[] args) throws MQClientException {
// 创建一个消息消费者,并设置一个消息消费者组
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer();
consumer.setConsumerGroup("order_consumer_group");
// 指定 NameServer 地址
consumer.setNamesrvAddr("192.168.44.128:9876;192.168.44.128:9877");
// 订阅指定 Topic 下的所有消息
consumer.subscribe("rocket_order_test_1", "*");
// 注册消息监听器
consumer.setConsumeMessageBatchMaxSize(10);
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
System.out.println(msgs);
for (MessageExt msg : msgs) {
try {
String s = new String(msg.getBody(), RemotingHelper.DEFAULT_CHARSET);
System.out.println(s);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
// 消费者对象在使用之前必需要调用 start 初始化
consumer.start();
System.out.println("消息消费者已启动");
}
}
3. メッセージ配信戦略
1. まず、上の図 (RocketMQ のメッセージ モデルの全体図):
トピック (メッセージ トピック) は複数の実際のメッセージ キュー (MessgeQueue) に対応する場合があります。基礎となる実装では、MQ の可用性と柔軟性を向上させるために、実際に本題は、格納処理においてはマルチキュー方式を採用しており、その具体的な形式は上図のとおりであり、各メッセージキューは先入れ先出し(FIFO、先入れ先出し)で消費される必要がある。やり方。
このモデルに基づいて、次の 2 つの疑問が生じます。
- プロデューサー: 同じトピックのメッセージを送信する場合、メッセージ本文はどのメッセージ キュー (MessageQueue) に配置されますか?
- Consumer : メッセージを消費するとき、どのメッセージ キューからメッセージを取得する必要がありますか?
1. プロデューサーデリバリー戦略
プロデューサー配信戦略は、メッセージをさまざまなキューに配信する方法に関するものです。
1.ポーリング アルゴリズム配信: デフォルトの配信方法 (Queue キュー ポーリング アルゴリズム配信に基づく)
2.順次配信戦略: 一部のシナリオでは、次のような同じタイプのメッセージの順次配信と消費を保証する必要があります。
たとえば、TOPIC topicTest があるとします。このトピックの下には 4 つのキュー キューがあります。このトピックは、注文のステータス変更を送信するために使用されます。注文のステータスは、未払い、支払い済み、出荷 (処理中)、出荷済みであると仮定します。成功しました、配送は失敗しました。
タイミングの観点から、プロデューサーは次のメッセージを生成できます。
注文 T0000001: 未払い --> 注文 T0000001: 支払い済み --> 注文 T0000001: 配送 (処理) --> 注文 T0000001: 配送失敗
メッセージが MQ に送信された後、おそらくポーリング配信により、MQ 内のメッセージ ストレージは次のようになります。
この場合、コンシューマーがメッセージを消費する順序が、メッセージを送信する順序と一致することを望みます。ただし、上記の MQ のような配信と消費の仕組みでは、順序が正しいかどうかは保証できませんし、順序が異常なメッセージについては、コンシューマーが一定の状態の耐障害性を備えていたとしても、非常に多くのランダムな組み合わせを完全に処理することはできません。
上記の状況に基づいて、RockeMQ は次の実装計画を採用します。同じ順序番号を持つメッセージについては、特定の戦略を通じてキューに配置され、その後、コンシューマーが特定の戦略を採用します (1 つのスレッドが 1 つのキューを独立して処理し、順序を保証します)。メッセージの処理の順序を確認できます)、消費の順序を保証できます
メッセージ配信プロセス中に、プロデューサーはキュー選択の戦略インターフェイスとして MessageQueueSelector を使用します。これは次のように定義されます。
public interface MessageQueueSelector {
/**
* 根据消息体和参数,从一批消息队列中挑选出一个合适的消息队列
* @param mqs 待选择的MQ队列选择列表
* @param msg 待发送的消息体
* @param arg 附加参数
* @return 选择后的队列
*/
MessageQueue select(final List<MessageQueue> mqs, final Message msg, final Object arg);
}
3.独自の実装クラスが付属しています。
配信戦略 | 戦略実装クラス | 説明する |
---|---|---|
ランダム割り当て戦略 | ランダムでメッセージキューを選択 | 単純な乱数選択アルゴリズムを使用します |
ハッシュ割り当て戦略に基づく | SelectMessageQueueByHash | 追加パラメータのハッシュ値に応じて、メッセージキューリストのサイズに応じた余りを取り、メッセージキューのインデックスを取得します。 |
機械室の位置に基づいた割り当て戦略 | SelectMessageQueueByMachineRoom | オープンソース バージョンには特別な実装はありませんが、基本的な目的は近接原則に基づいてマシンを分散することです。 |
2. コンシューマ割り当てキュー
1. RocketMQ には、コンシューマーがメッセージを消費するための 2 つの形式があります。
- ブロードキャスト: ブロードキャスト消費: このモードでは、メッセージがすべてのコンシューマに通知されます。
- CLUSTERING: クラスタ化された消費。このモードでは、メッセージは最大でも 1 つのコンシューマにのみ配信されます。消費モードは次のとおりです。消費モード MessageModel.CLUSTERING を使用して消費する場合は、メッセージが 1 回だけ消費される必要があることを確認する必要があります
。実際、RoketMQ の下部では、コンシューマへのメッセージ割り当ての実装は、キュー キューを介してコンシューマにメッセージを割り当てることによって実現されます。つまり、メッセージ配布の単位は、メッセージが配置されているキュー キューです。
キューを特定のコンシューマに割り当てると、キュー内のすべてのメッセージがそのコンシューマに割り当てられて消費されます。
RocketMQ は、戦略インターフェイス AllocateMessageQueueStrategy を定義します。特定のコンシューマ グループ、メッセージ キュー リスト、およびコンシューマ リストに対して、現在のコンシューマがどのキュー キューに割り当てられるかは次のように定義されます。
/**
* 为消费者分配queue的策略算法接口
*/
public interface AllocateMessageQueueStrategy {
/**
* Allocating by consumer id
*
* @param consumerGroup 当前 consumer群组
* @param currentCID 当前consumer id
* @param mqAll 当前topic的所有queue实例引用
* @param cidAll 当前 consumer群组下所有的consumer id set集合
* @return 根据策略给当前consumer分配的queue列表
*/
List<MessageQueue> allocate(
final String consumerGroup,
final String currentCID,
final List<MessageQueue> mqAll,
final List<String> cidAll
);
/**
* 算法名称
*
* @return The strategy name
*/
String getName();
}
したがって、RocketMQ は次の実装を提供します。
アルゴリズム名 | 意味 |
---|---|
メッセージキューを平均的に割り当てる | 均等分配アルゴリズム |
AllocateMessageQueueAveragelyByCircle | リング平均分布アルゴリズムに基づく |
マシンを割り当てる近くの部屋 | コンピュータ室の近接原理に基づくアルゴリズム |
AllocateMessageQueueByMachineRoom | コンピュータ室割り当てアルゴリズムに基づく |
AllocateMessageQueueConsistentHash | 一貫したハッシュ アルゴリズムに基づく |
AllocateMessageQueueByConfig | 構成ベースの割り当てアルゴリズム |
2.平均割り当てアルゴリズム: ここでのいわゆる平均割り当てアルゴリズムは、厳密な意味での完全な平均を指しません。たとえば、上記の例では、キューは 10 個ありますが、コンシューマは 4 つしかありません。整数はありません。整数の除算を除く、割り算関係。追加のキューはコンシューマーの順序に従って共有されます。
上記の例によれば、10/4=2 は、各コンシューマに平均 2 つのキューが割り当てられることを意味し、10%4=2 は、均等な割り当てに加えて、追加の 2 つのキューがまだ割り当てられていないことを意味します。次に、消費に従って、順序がconsumer-1、consumer-2、consumer-3、consumer-4の場合、2つの追加のキューがそれぞれconsumer-1とconsumer-2に与えられます。最終的なレンダリングは次のとおりです。
3.リング平均割り当て: リング平均アルゴリズムは、コンシューマの順序に従ってキューで構成されるリング グラフに 1 つずつ割り当てることを指します。
このアルゴリズムの最終的な分散結果は次のとおりです。
Consumer-1: #0、#4、#8
Consumer-2: #1、#5、#9
Consumer-3: #2、#6
Consumer-4: #3、# 7
4. RocketMQ メッセージの保証
1.生産終了保証
本番エンドのセキュリティは、次の側面から保証する必要があります。
- 信頼できるメッセージングを使用する
- 本番側のリトライに注意
- プロダクションではトピックの自動作成が禁止されています
1. メッセージ配信の保証
- 同期送信: 同期送信とは、プロデューサーがメッセージを送信した後、ブローカーからの応答を受信した後、次のメッセージの送信を続けることを意味します。
使用するシーン:
この同期送信方法は、メッセージの信頼性を確保し、メッセージ送信結果を時間内に取得できるため、重要な通知メールやマーケティング テキスト メッセージなど、より重要なメッセージを送信する一部のシナリオに適しています。 、この同期送信方法は今でも一般的に使用されています。
予防:
このメソッドには内部再試行メカニズムがあります。つまり、メッセージ送信が失敗したことを積極的に宣言する前に、内部実装は特定の回数再試行します。デフォルトは 2 回 (DefaultMQProducer#getRetryTimesWhenSendFailed)、同じメッセージが複数回送信される可能性があります。ブローカーの場合、アプリケーション開発者はコンシューマ側の冪等性の問題に対処する必要があります。
- 非同期送信: 非同期送信とは、プロデューサーがメッセージを送信した後、ブローカーの応答を待つ必要がなく、次のメッセージを送信することを意味します。ブローカーの応答を待たないことは、ブローカーは応答しませんが、ブローカーの応答を受信するためにコールバック インターフェイスが使用されるため、非同期送信ではメッセージの応答結果も処理できることに注意してください。
使用するシーン:
非同期送信はブローカーの応答を待つ必要がないため、RT (応答時間) に注意を払う一部のシナリオに適しています。たとえば、一部のビデオのアップロード シナリオでは、ビデオのアップロード後にトランスコーディングが必要であることがわかっています。同期送信を使用する場合トランスコードサービスの開始を通知するメソッドを使用する場合、トランスコード結果の応答を返すためにトランスコードの完了を待つ必要があり、トランスコード時間が長くなることが多いため、このとき、非同期送信通知トランスコードサービスを利用すると、トランスコードの完了を待って、コールバックインターフェースを通じてトランスコード結果の応答を受け取ることができます。
2. メッセージ送信の概要
- 送信方法の比較
送信方法 | TPS を送信する | 結果のフィードバックを送信する | 信頼性 | 該当シーン |
---|---|---|---|---|
同期送信 | 一般的に | 持っている | 失われていない | 重要な通知シナリオ |
非同期で送信する | 素早い | 持っている | 失われていない | RT(応答時間)をより重視したシナリオ |
一方通行の送信 | 最速の | なし | 失われるかもしれない | 信頼性要件が高くないシナリオ |
- 利用シーンの比較
実際の利用シーンでどの送信方式が使用されるかをまとめると、次のようになります。
1. 送信するメッセージが重要でない場合、スループットを向上させるために一方向方式が使用されます。
2. 送信されるメッセージが重要で、応答時間が短い場合。影響を受ける 重要でない場合は同期メソッドを使用します
3. 送信されたメッセージが重要であり、応答時間に非常に敏感な場合は、非同期メソッドを使用します。
3.送信ステータス
メッセージを送信すると、SendStatus を含む SendResult を取得します。まず、Message の isWaitStoreMsgOK = true (デフォルトは true) であると仮定します。例外がスローされなければ、常に SEND_OK を取得します。各ステータスの説明は次のとおりです。
- FLUSH_DISK_TIMEOUT : FlushDiskType=SYNC_FLUSH が設定されており (デフォルトは ASYNC_FLUSH)、ブローカーが syncFlushTimeout で設定された時間内にフラッシュを完了しない場合 (デフォルトは 5 秒)、このステータス コードを受け取ります。
- FLUSH_SLAVE_TIMEOUT : SYNC_MASTER に設定され、スレーブ ブローカーが syncFlushTimeout 設定時間内に同期を完了しない場合、このステータス コードが受信されます。
- SLAVE_NOT_AVAILABLE : SYNC_MASTER に設定され、スレーブ ブローカーが構成されていない場合、このステータス コードを受け取ります。
- SEND_OK : このステータスは、上記の 3 つの問題が発生していない場合、単純に SEND_OK として理解できます。SEND_OK は信頼性を意味するものではないことに注意してください。メッセージが失われないことを厳密に保証したい場合は、SYNC_MASTER をオンにするか、同期フラッシュ。
- 注: FLUSH_DISK_TIMEOUT、FLUSH_SLAVE_TIMEOUT を受信した場合、メッセージが失われることを意味します。オプションは 2 つあります。1 つは無関心で、メッセージが重要ではないシナリオに適しています。2 つ目は再送信ですが、メッセージが表示される可能性があります。重複では、コンシューマーが重複排除制御を実行する必要があります。SLAVE_NOT_AVAILABLE を受け取った場合は、すぐに管理者に通知してください。
4.MQ 送信側の再試行保証: ネットワーク ジッターやその他の理由により、プロデューサー プログラムがブローカーへのメッセージの送信に失敗した場合、つまり、送信側がブローカーから ACK を受信できず、その結果、最終的なコンシューマーはメッセージを送信できなくなります。メッセージを消費すると、この時点で RocketMQ が自動的に再試行します。
DefaultMQProducer は、失敗したメッセージ送信の最大再試行回数を設定し、送信タイムアウトと組み合わせて再試行を処理できます。具体的な API は次のとおりです。
//设置消息发送失败时的最大重试次数
public void setRetryTimesWhenSendFailed(int retryTimesWhenSendFailed) {
this.retryTimesWhenSendFailed = retryTimesWhenSendFailed;
}
//同步发送消息,并指定超时时间
public SendResult send(Message msg,
long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return this.defaultMQProducerImpl.send(msg, timeout);
- 問題を解決するためにもう一度試してください。
インターネット上の多くの場所では、タイムアウト例外が間違った後に再試行が行われると記載されています。 |
これは、以下のテストコードのタイムアウトが 5 ミリ秒に設定されているためです。通常、タイムアウト例外が報告されます。ただし、リトライ 1 回と 3000 回のリトライを設定すると、最終的に次の例外が報告されますが、出力されたエラー時間レポートは明らかに A レベルではないはずですが、テストの結果、再試行の回数が設定されていても、例外が報告されるまでの時間はほぼ同じであることがわかりました。
- テストコード
public class RetryProducer {
public static void main(String[] args) throws UnsupportedEncodingException, InterruptedException, RemotingException, MQClientException, MQBrokerException {
//创建一个消息生产者,并设置一个消息生产者组
DefaultMQProducer producer = new DefaultMQProducer("rocket_test_consumer_group");
//指定 NameServer 地址
producer.setNamesrvAddr("127.0.0.1:9876");
//设置重试次数(默认2次)
producer.setRetryTimesWhenSendFailed(300000);
//初始化 Producer,整个应用生命周期内只需要初始化一次
producer.start();
Message msg = new Message(
/* 消息主题名 */
"topicTest",
/* 消息标签 */
"TagA",
/* 消息内容 */
("Hello Java demo RocketMQ ").getBytes(RemotingHelper.DEFAULT_CHARSET));
//发送消息并返回结果,设置超时时间 5ms 所以每次都会发送失败
SendResult sendResult = producer.send(msg, 5);
System.out.printf("%s%n", sendResult);
// 一旦生产者实例不再被使用则将其关闭,包括清理资源,关闭网络连接等
producer.shutdown();
}
}
- 再試行の概要:
- 非同期で送信される場合、デフォルトの再試行回数は 2 回であり、再試行は再帰的に実行されます。
- 同期の場合、タイムアウト例外は再試行されません。
- 同期送信再試行は for ループ内で再試行するため、しばらくしてから再試行するのではなく、すぐに再試行します。
5. トピックの自動作成を無効にする
- .TOPICプロセスの自動作成
- メッセージ送信時にトピックに従って経路情報が取得されなかった場合は、デフォルトのトピック(TBW102)に従って経路情報が取得されます。経路情報取得後、送信するキューを選択してください。送信時、メッセージは、デフォルトのトピックとデフォルトのキュー数
- メッセージがブローカーに到達すると、ブローカーはトピックのルーティング情報がないことを検出し、デフォルトのトピックのルーティング情報を検索します。見つかった場合は、トピックの自動作成が有効になっていることを意味し、トピックはメッセージコンテンツ内のデフォルトのキュー数に基づいて、このブローカー上にメッセージを作成してから続行します。
- ブローカーがトピックを作成した後、そのトピックは namesrv にすぐには同期されません。代わりに、30 秒ごとにレポートを作成して、namesrv 上のトピック ルーティング情報を更新します。プロデューサは、30 秒ごとにトピック ルーティング情報を取得します。更新が完了した後、 , メッセージは正常に送信できますが、アップデート前は常にデフォルトのトピックに基づいてルーティング情報が検索されていました。
- 自動作成をオンにできないのはなぜですか?
- 上記のブローカーのプロセスに問題があります。つまり、プロデューサがルーティング情報を更新するまでの期間、メッセージがブローカー a にのみ送信されると、このトピックのルーティング情報がブローカー b に作成されません。プロデューサーが更新されると、ブローカー リストはブローカー a のみが取得され、ブローカー b のキューはポーリングされません (ルーティング情報がないため)。通常はブローカーの自動作成をオフにしますが、手動作成を使用します。
6. 原点回避
1. ここで、実際の運用プロセスでは、RocketMQ が複数のサーバーで構成されるクラスターを持つ可能性があることがわかります 2. トピック TopicA 内の 4 つのキューが、
Broker1、Broker2、および Broker2 に格納されている可能性があります。 Broker3 サーバー。優れたサーバー
この時点で Broker2 がハングアップした場合はわかりますが、プロデューサーにはわかりません (プロデューサー クライアントは 30 秒ごとにルートを更新しますが、NamServer と Broker 間のハートビート検出間隔は 10 秒なので、プロデューサーがセンシングするまでに最速で 30 秒必要です) Broker2 がダウンしているため)、queue2 に送信されたメッセージは失敗します。RocketMQ は、メッセージの送信に失敗したことを検出した後、メッセージ選択範囲から Broker2 を除外します。次回メッセージが再送信されるとき、メッセージは Broker2 には送信されません。このようにしてメッセージ送信の成功率を向上させることが目的です
- 問題の整理:例えば、送信前にsendWhichQueueの値がbroker-aのq1であり、このときbroker-aのバーストトラフィックが異常に多くメッセージ送信に失敗した場合、リトライが発生します。 -robin メカニズム、次に選択されるキュー ブローカー a の q2 キューの場合、メッセージは依然として失敗する可能性が高くなります。つまり、メッセージは 2 回再試行されますが、処理のためにすべて同じブローカーに送信されます。このプロセスは信頼性が低いように見えます。つまり、メッセージが依然として失敗する可能性が高く、失敗した場合、再試行の意義は大幅に減少します。したがって、この問題を解決するために、RocketMQ は障害回避メカニズムを導入しています. メッセージを再試行するとき、前回送信されたブローカーを回避しようとします. 上の例に戻ると、メッセージがブローカー a に送信されるときq1 キューの場合、送信失敗が返されるので、再試行します。試行すると、最初にブローカー a のすべてのキューが除外され、メッセージ送信の成功率を高めるために、今回はブローカー b の q1 キューが選択されます。
- ルール戦略: ただし、RocketMQ は 2 つの回避戦略を提供します。このパラメーターは sendLatencyFaultEnable によって制御されます。ユーザーは遅延回避メカニズムを有効にするかどうかを示すために介入できます。デフォルトは有効になっていません。(これら 2 つのパラメータを DefaultMQProducer で設定します)
- sendLatencyFaultEnable は false に設定されます: デフォルト値は有効ではなく、遅延回避戦略は再試行時にのみ有効になります。たとえば、メッセージ送信プロセス中にメッセージの送信に失敗した場合、broekr-a は回避されますが、次の場合にはメッセージが送信されると、再度呼び出されます。 DefaultMQProducer の send メソッドがメッセージを送信するとき、送信に失敗し続ける限り、ブローカー a のメッセージを選択して送信します。再試行時にブローカー a を再度回避します。
- sendLatencyFaultEnable が true に設定されている: 遅延回避メカニズムをオンにします。メッセージの送信に失敗すると、broker-a は次の期間は Broker が利用できなくなると「悲観的に」考慮し、すべてのクライアントがメッセージを送信しなくなります。将来の一定期間クライアントに送信します。ブローカーがメッセージを送信するとき、遅延時間は notAvailableDuration と latencyMax を通じて計算されます。まず、このメッセージの送信の失敗によって消費される遅延を計算し、次にどの間隔に対応しますかin latencyMax、つまり、latencyMax の添字を計算し、notAvailableDuration を返します。同じ添字に対応する遅延値
- 注: すべてのブローカーが障害回避をトリガーし、ブローカーがその時点で大きなプレッシャーにさらされている場合、明らかに利用可能なブローカーが存在しますが、それを回避した後は利用可能なブローカーが存在しません。この問題を解決するために、キューのラウンドロビン機構に縮退します。つまり、障害回避の要素を考慮せずに、自然な順序で選択が実行されます。
2. 消費者保護
1.冪等性に注意してください。同じメッセージを複数回消費すると、1 回消費した場合と同じ結果になります。
- 少なくとも 1 回の配信: メッセージ配信戦略には、メッセージの繰り返し消費と共生的な因果関係があります。メッセージの損失を避けるために、メッセージの繰り返し消費は避けられません。理由は非常に簡単です。次のシナリオを想像してみてください: クライアントはメッセージを受信し、消費が完了しましたが、消費確認プロセスで通信エラーが発生しました。ブローカーの観点からは、クライアントがメッセージ受信プロセスでエラーを起こしたのか、消費確認プロセスでエラーが発生したのかを知ることはできません。紛失したわけではないので、メッセージを再送信することが唯一の選択肢です
- メッセージの再送信は避けられないため、ビジネス担当者はメッセージの冪等性を自分で設計する必要があります。メッセージ冪等消費合意の基礎により、ビジネス上の問題が解決され、RocketMQ にもメリットがもたらされます。RocketMQ は、並列消費、消費進行同期メカニズムなど、ターゲットを絞ったパフォーマンスの最適化措置を講じることができます。これも、RocketMQ の優れたパフォーマンスの理由の 1 つです。
2.消費者グループの消費モード: さまざまな次元から分割され、消費者は次の消費モードをサポートします。
- ブロードキャスト消費モード: メッセージ消費の失敗は再試行されず、消費の進行状況はコンシューマ側に保存されます。
- クラスター消費モード: メッセージの消費が失敗した場合、再試行する機会があり、消費の進行状況はブローカー側で一元的に保存されます。
- クラスターの消費
同じグループ ID を使用するサブスクライバは同じクラスタに属しており、同じクラスタ内のサブスクライバの消費ロジックは完全に一貫している必要があります (タグの使用を含む)。これらのサブスクライバは論理的に消費ノードと見なすことができます。
予防:
1. コンシューマ側クラスタの展開。各メッセージは 1 回だけ処理する必要があります。
2. 消費経過はサーバー側で保持されるため、信頼性が高くなります。
3. クラスター消費モードでは、各メッセージは処理のために 1 台のマシンにのみ配布されます。クラスター内のすべてのマシンで処理する必要がある場合は、ブロードキャスト モードを使用してください。
4. クラスター消費モードでは、失敗したすべての再配信メッセージが同じマシンにルーティングされるという保証はないため、メッセージを処理するときに決定的な仮定を行う必要はありません。
- ブロードキャスト消費
ブロードキャスト消費とは、メッセージが複数のコンシューマによって消費されることを指します。これらのコンシューマが同じ ConsumerGroup に属している場合でも、メッセージは ConsumerGroup 内の各 Consumer によって 1 回消費されます。ブロードキャスト消費における ConsumerGroup の概念は、次の点で無意味であると考えられます。メッセージ部門。
予防:
1. 連続メッセージはブロードキャスト消費モードではサポートされません
2. 消費位置のリセットはブロードキャスト消費モードではサポートされません
3. 各メッセージは同じロジックを備えた複数のマシンで処理する必要があります 4.
消費の進行状況は5.ブロードキャスト モードでは、
メッセージ キュー RocketMQ は、各メッセージが各クライアントによって少なくとも 1 回消費されることを保証しますが、6. 処理に失敗したメッセージケース
7. ブロードキャスト モードでは、クライアントは再起動するたびに最新のメッセージを消費します。
8. ブロードキャスト モードでは、各メッセージは多数のクライアントによって繰り返し処理されるため、できるだけクラスター モードを使用することをお勧めします。できるだけ。
9. 現在、ブロードキャスト モードをサポートしているのは Java クライアントのみです
10. ブロードキャスト モードでは、サーバーは消費の進行状況を維持しないため、メッセージ キュー RocketMQ コンソールは、メッセージ蓄積クエリ、メッセージ蓄積アラーム、およびサブスクリプション関係クエリ機能をサポートしません。
- クラスターモードのアナログブロードキャスト
ビジネスでブロードキャスト モードを使用する必要がある場合は、複数のグループ ID を作成して同じトピックにサブスクライブすることもできます。
- プッシュ メッセージ モード: 消費の進行状況の増分は RocketMQ によって内部的に自動的に維持されます。
- プル メッセージ モード: 消費進行状況の変更には、上位層アプリケーションがメンテナンスを担当する必要があります。RocketMQ は、消費進行状況の保存とクエリ機能のみを提供します。
プッシュモード(PUSH)
メッセージ ミドルウェア (MQ メッセージ サーバー エージェント) は、メッセージをコンシューマにアクティブにプッシュします。プッシュ メソッドを使用すると、メッセージを可能な限りリアルタイムで消費するためにコンシューマに送信できます。ただし、コンシューマのメッセージ処理能力が弱い場合 (たとえば、コンシューマ側のビジネス システムによるメッセージの処理プロセスが複雑になり、呼び出しリンクが多くなり、消費時間が長くなります)。が「遅い」「消費の問題」)、MQ が継続的にメッセージをコンシューマにプッシュすると、コンシューマ側のバッファがオーバーフローして例外が発生する可能性があります。
プルモード(PULL)
コンシューマ クライアントは、メッセージ ミドルウェア (MQ メッセージ サーバー エージェント) からメッセージをアクティブにプルします。プル メソッドを使用する場合、プル メッセージの頻度を設定する方法を慎重に検討する必要があります。たとえば、1 分以内に 1,000 件のメッセージが連続して送信される可能性があります。メッセージ、その後、2 時間以内に新しいメッセージは生成されません (要約すると、「メッセージの遅延と待機中」です)。各プル間の時間間隔が比較的長い場合、メッセージの遅延が増加します。つまり、メッセージがコンシューマに到達するまでの時間が長くなり、MQ 内のメッセージの蓄積が大きくなります。各プルの間隔は短くなりますが、一定期間内に MQ は消費するメッセージがないため、無効なプル リクエストの RPC オーバーヘッドが大量に生成され、MQ の全体的なネットワーク パフォーマンスに影響を与えます。
- メッセージ確認の仕組み
- 消費の確認: ビジネスが消費コールバックを実装すると、RocketMQ は、このコールバック関数が ConsumeConcurrentlyStatus.CONSUME_SUCCESS を返した場合に限り、このメッセージのバッチ (デフォルトは 1) が消費されるとみなします。
- 消費例外: データベースの異常、残高不足の控除の失敗など、この時点でメッセージの消費が失敗した場合、すべてのビジネスはメッセージを再試行する必要があると判断します。ConsumeConcurrentlyStatus.RECONSUME_LATER が返される限り、RocketMQ はメッセージの消費を考慮します。このメッセージのバッチは失敗しました。
- メッセージ再試行メカニズム
- 連続メッセージの再試行: 連続メッセージの場合、コンシューマーがメッセージの消費に失敗すると、RocketMQ メッセージ キューは自動的かつ継続的にメッセージを再試行します (各間隔は 1 秒)。この時点で、アプリケーションはメッセージの消費がブロックされているように見えます。連続メッセージを使用する場合は、ブロックを避けるために、アプリケーションが消費エラーをタイムリーに監視および処理できることを確認する必要があります。
- 順序が崩れたメッセージの再試行: 順序が乱れたメッセージの再試行は、クラスター消費モードでのみ有効です。ブロードキャスト モードには失敗時の再試行機能がありません。つまり、消費が失敗した後、失敗したメッセージは再試行されません。再試行されても、新しいメッセージは引き続き消費されます
- 再試行回数: RocketMQ メッセージ キューでは、デフォルトで各メッセージを最大 16 回再試行できます。各再試行の間隔は次のとおりです。
何回再試行するか | 最後の再試行間の時間 | 何回再試行するか | 最後の再試行間の時間 |
---|---|---|---|
1 | 10秒 | 9 | 7分 |
2 | 30秒 | 10 | 8分 |
3 | 1分 | 11 | 9分 |
4 | 2分 | 12 | 10分 |
5 | 3分 | 13 | 20分 |
6 | 4分 | 14 | 30分 |
7 | 5分 | 15 | 1時間 |
8 | 6分 | 16 | 2時間 |
-
如果消息重试16次后仍然失败,消息将不再投递。如果严格按照上述重试时间间隔计算,某条消息在一直消费失败的前提下,将会在接下来的4小时46分钟之内进行16次重试,超过这个时间范围消息将不再重试投递
-
生产端重试区别:消费者和生产者的重试还是有区别的,主要有两点:
- 默认重试次数:Product默认是2次,而Consumer默认是16次
- 重试时间间隔:Product是立刻重试,而Consumer是有一定时间间隔的
- 注意:而对于Consumer在广播情况下重试失效
-
重试配置方式:消费失败后,重试配置方式,集群消费方式下,消息消费失败后期望消息重试,需要在消息监听器接口的实现中明确进行配置(三种方式任选一种)
- 方式1:返回RECONSUME_LATER(推荐)
- 方式2:返回Null
- 方式3:抛出异常
-
无需重试:集群消费方式下,消息失败后期望消息不重试,需要捕获消费逻辑中可能抛出的异常,最终返回Action.CommitMessage,此后这条消息将不会再重试
3.死信队列
在正常情况下无法被消费(超过最大重试次数)的消息称为死信消息(Dead-Letter Message),存储死信消息的特殊队列就称为死信队列(Dead-Letter Queue)
当一条消息初次消费失败,消息队列 RocketMQ 会自动进行消息重试。当达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息。此时,消息队列 RocketMQ 不会立刻将消息丢弃,而是将其发送到该消费者对应的特殊队列中。 在RocketMQ中,这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)
- 死信消息特性
- 不会再被消费者正常消费
- 有效期与正常消息相同,均为 3 天。3 天后会被自动删除,故死信消息应在产生的 3 天内及时处理
- 死信队列特性
- 一个死信队列对应一个消费者组,而不是对应单个消费者实例
- デッドレター キューには、メッセージがどのトピックに属しているかに関係なく、対応するグループ ID によって生成されたすべてのデッドレター メッセージが含まれます。
- グループ ID がデッドレター メッセージを生成していない場合、RocketMQ はそれに対応するデッドレター キューを作成しません。
5.Redisポーリングキュー(拡張)
車両情報は redis キューに格納されており、配車システムはキューから車両情報を取得し、タクシーが到着した後に車両情報をキューに戻します。
関連コード:
- Redis から車両を取得します (リストの左側から車両をポップアップします)。
/**
* 从Redis List列表中拿取一个车辆ID
* 如果没有获取到延时10S
*
* @return
*/
public String takeVehicle() {
//从Redis List列表中拿取一个车辆ID
return redisTemplate.opsForList().leftPop(DispatchConstant.VEHICLE_QUEUE, 1, TimeUnit.SECONDS);
}
2. Redisを車両に押し込みます(車両の状態を確認し、右側から車両に押し込みます)
/**
* 设置车辆状态为Ready
*
* @param vehicleId
*/
public void readyDispatch(String vehicleId) {
//检查车辆状态
DispatchConstant.DispatchType vehicleDispatchType = taxiVehicleStatus(vehicleId);
//如果车辆时运行状态
if (vehicleDispatchType.isRunning() || vehicleDispatchType.isReady()) {
redisTemplate.opsForValue().set(DispatchConstant.VEHICLE_STATUS_PREFIX + vehicleId, DispatchConstant.DispatchType.READY.toString());
//从右侧压入车辆
redisTemplate.opsForList().rightPush(DispatchConstant.VEHICLE_QUEUE, vehicleId);
}
}