web-dev-qa-db-ja.com

RabbitMQ-配信のメッセージ順序

新しいプロジェクト用に新しいキューブローカーを選択する必要があります。

今回は、pub/subをサポートするスケーラブルキューが必要です。メッセージの順序を維持することは必須です。

私はアレクシスのコメントを読みました:彼は書いています:

「実際、RabbitMQはKafkaよりも強力な順序付けを提供すると考えています」

Rabbitmqドキュメントのメッセージ注文セクションを読みました。

「メッセージは、requeueパラメーター(basic.recover、basic.reject、basic.nack)を備えたAMQPメソッドを使用して、または未確認のメッセージを保持している間にチャネルを閉じることにより、キューに戻すことができます。リリース2.7.0以降キューに複数のサブスクライバーがある場合、個々のコンシューマーがメッセージを順不同で監視する可能性があります。これは、メッセージをリキューできる他のサブスクライバーのアクションによるものです。キューの観点から、メッセージは常にパブリケーションの順序で保持されます。 」

メッセージをその順序で処理する必要がある場合、rabbitMQを使用できるのは各コンシューマへの排他キューのみです。

RabbitMQは、順序付きメッセージキューイングの優れたソリューションと考えられていますか?

46
Bick

さて、上記のシナリオを詳しく見てみましょう。コンテキストを提供するには、質問のスニペットの直前に ドキュメント を貼り付けることが重要だと思います。

AMQP 0-9-1コア仕様のセクション4.7では、順序が保証される条件について説明しています。1つのチャネルで発行され、1つの交換、1つのキュー、1つの発信チャネルを通過するメッセージは、送信された順序で受信されます。 RabbitMQは、リリース2.7.0以降、より強力な保証を提供します。

メッセージは、再キューイングパラメーター(basic.recover、basic.reject、basic.nack)を備えたAMQPメソッドを使用して、または未確認のメッセージを保持しながらチャネルを閉じることにより、キューに戻すことができます。これらのシナリオのいずれかにより、2.7.0より前のRabbitMQリリースでは、キューの後ろでメッセージが再キューイングされました。 RabbitMQリリース2.7.0以降、メッセージは、リキューまたはチャネルクロージャが存在する場合でも、常にパブリケーション順にキューに保持されます。(強調が追加されました)

したがって、2.7.0以降のRabbitMQは、メッセージの順序に関して、元のAMQP仕様よりも大幅に改善されていることは明らかです。

複数の(並列)コンシューマーでは、処理の順序を保証できません。
3番目の段落(質問に貼り付けられています)は免責事項を示しています。言い換えると、「キューに複数のプロセッサがある場合、メッセージが順番に処理されるという保証はありません。 」彼らがここで言っているのは、RabbitMQが数学の法則を無視できないということだけです。

銀行の顧客を考えてみましょう。この特定の銀行は、顧客が銀行に入ってきた順序で顧客を支援することに誇りを持っています。顧客は列に並んでおり、3人の利用可能な窓口の次の係員が対応します。

今朝、たまたま3人のテラー全員が同時に対応できるようになり、次の3人の顧客が近づいてきました。突然、3人の窓口のうち最初の社員が激しく病気になり、最初の顧客にサービスを提供できなくなりました。これが発生するまでに、窓口2は顧客2で終了し、窓口3はすでに顧客3に対応し始めていました。

現在、次の2つのいずれかが発生します。 (1)行の最初の顧客が行の先頭に戻るか、(2)最初の顧客が3番目の顧客を先取りして、その窓口係が3番目の顧客の作業を停止し、最初の作業を開始できます。このタイプのプリエンプションロジックは、RabbitMQや、私が知っている他のメッセージブローカーではサポートされていません。どちらの場合でも、最初の顧客は実際には最初に助けられることはありません-2番目の顧客は幸運で、すぐに良いテラーを得ることができます。顧客を確実に支援する唯一の方法は、1人の窓口係が顧客を1人ずつ支援することです。これにより、銀行の主要な顧客サービスの問題が発生します。

これがあなたが尋ねている問題を説明するのに役立つことを願っています。複数のコンシューマがある場合、考えられるすべてのケースでメッセージが順番に処理されることを保証することはできません。複数のキュー、複数の排他的コンシューマー、異なるブローカーなどがあるかどうかは関係ありません-複数のコンシューマーでメッセージが順番に応答されることをprioriiを保証する方法はありません。しかし、RabbitMQは最善を尽くします。

114
theMayer

メッセージの順序は、Kafkaで保持されますが、グローバルではなくパーティション内でのみ保持されます。データにグローバルな順序付けとパーティションの両方が必要な場合、これは事態を困難にします。ただし、同じユーザーなどの同じイベントがすべて同じパーティションに配置され、それらが適切に順序付けられるようにする必要がある場合は、そうすることができます。プロデューサーは書き込み先のパーティションを管理するため、データを論理的にパーティション化できる場合はこれが望ましい場合があります。

7
tyler neely

この質問には、消費順序と処理順序の2つの類似点はないと思います。

メッセージキューは、ある程度まではメッセージが順番に消費されることを保証できますが、処理の順序を保証することはできません。

ここでの主な違いは、消費時に決定できないメッセージ処理のいくつかの側面があることです。例えば:

  • 前述のように、処理中にコンシューマーが失敗する可能性がありますが、ここではメッセージの消費順序は正しいものでしたが、コンシューマーはそれを正しく処理できなかったため、キューに戻りますが、これまで消費順序はそのままでしたが、 t処理順序が現在どのようになっているかを知る

  • 「処理」とは、メッセージが破棄されて処理が完全に終了することを意味する場合、処理時間が線形でない場合、つまり、1つのメッセージの処理に時間がかかるため、メッセージ3の処理に時間がかかる場合を考慮してください予想されると、メッセージ4と5が消費され、メッセージ3が処理される前に処理が終了する可能性があります

そのため、メッセージをキューの先頭に戻すことができたとしても(消費順序に違反します)、次のメッセージの処理が完了する前にすべてのメッセージを保証することはできません。

処理順序を確認する場合:

  1. 常に1つのコンシューマインスタンスのみが存在する
  2. または、メッセージングキューを使用せず、同期ブロッキング方式で処理を実行します。これは悪いように聞こえるかもしれませんが、多くの場合、ビジネス要件は完全に有効であり、場合によっては重要ですらあります。
3
engma

RabbitMQサブスクリプション内のメッセージの順序を保証する適切な方法があります。

複数のコンシューマを使用する場合、それらは共有ExecutorServiceを使用してメッセージを処理します。 ConnectionFactory.setSharedExecutor(...)もご覧ください。 Executors.newSingleThreadExecutor()を設定できます。

1つのConsumerを1つのキューで使用する場合、複数のbindingKeysを使用してこのキューをバインドできます(ワイルドカードが含まれる場合があります)。メッセージは、メッセージブローカーによって受信されたのと同じ順序でキューに入れられます。

たとえば、順序が重要な場合にメッセージを発行する単一の発行元があります。

try (Connection connection2 = factory.newConnection();
        Channel channel2 = connection.createChannel()) {
    // publish messages alternating to two different topics
    for (int i = 0; i < messageCount; i++) {
        final String routingKey = i % 2 == 0 ? routingEven : routingOdd;
        channel2.basicPublish(exchange, routingKey, null, ("Hello" + i).getBytes(UTF_8));
    }
}

ここで、queueの両方のtopicsからメッセージを受信したい場合がありますそれらが公開されたのと同じ順序:

// declare a queue for the consumer
final String queueName = channel.queueDeclare().getQueue();

// we bind to queue with the two different routingKeys
final String routingEven = "even";
final String routingOdd = "odd";
channel.queueBind(queueName, exchange, routingEven);
channel.queueBind(queueName, exchange, routingOdd);
channel.basicConsume(queueName, true, new DefaultConsumer(channel) { ... });

Consumerは、異なるtopicsを使用したという事実に関係なく、発行された順にメッセージを受信します。

RabbitMQのドキュメントには、役に立つ5分間のチュートリアルがいくつかあります。 https://www.rabbitmq.com/tutorials/tutorial-five-Java.html

1
benez