web-dev-qa-db-ja.com

RabbitMQ:速いプロデューサーと遅いコンシューマー

RabbitMQをメッセージキューとして使用して、送信側と受信側の2つのコンポーネント間でメッセージを送受信するアプリケーションがあります。送信者は非常に高速な方法でメッセージを送信します。受信者はメッセージを受信し、いくつかの非常に時間のかかるタスク(主に非常に大きなデータサイズのデータ​​ベース書き込み)を実行します。受信者がタスクを完了してからキュー内の次のメッセージを取得するのに非常に長い時間がかかるため、送信者はキューをすばやくいっぱいにし続けます。だから私の質問です:これはメッセージキューをオーバーフローさせますか?

メッセージコンシューマは次のようになります。

public void onMessage() throws IOException, InterruptedException {
    channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
    String queueName = channel.queueDeclare("allDataCase", true, false, false, null).getQueue();
    channel.queueBind(queueName, EXCHANGE_NAME, "");

    QueueingConsumer consumer = new QueueingConsumer(channel);
    channel.basicConsume(queueName, true, consumer);

    while (true) {
        QueueingConsumer.Delivery delivery = consumer.nextDelivery();
        String message = new String(delivery.getBody());
        System.out.println(" [x] Received '" + message + "'");

        JSONObject json = new JSONObject(message);
        String caseID = json.getString("caseID");
        //following takes very long time            
        dao.saveToDB(caseID);
    }
}

コンシューマが受信する各メッセージには、caseIDが含まれています。各caseIDに対して、大量のデータがデータベースに保存されますが、これには非常に長い時間がかかります。現在、RabbitMQには1つのコンシューマーのみが設定されています。プロデューサー/コンシューマーは、caseIDのパブリッシュ/サブスクライブに同じキューを使用するためです。では、コンシューマのスループットをスピードアップして、コンシューマがプロデューサに追いつき、キューでのメッセージオーバーフローを回避できるようにするにはどうすればよいでしょうか。消費率を上げるために、コンシューマ部分でマルチスレッドを使用する必要がありますか?または、複数のコンシューマを使用して着信メッセージを同時に消費する必要がありますか?または、コンシューマがメッセージの完了を待たずに非同期で消費できるようにする非同期の方法はありますか?どんな提案でも大歓迎です。

18
tonga

「これによりメッセージキューがオーバーフローしますか?」

はい。 RabbitMQは「フロー制御」の状態になり、キューの長さが増加するときに過度のメモリ消費を防ぎます。また、メッセージをメモリに保持するのではなく、メッセージをディスクに永続化し始めます。

「それでは、コンシューマーのスループットをスピードアップして、コンシューマーがプロデューサーに追いつき、キューでのメッセージのオーバーフローを回避できるようにするにはどうすればよいですか。」

次の2つのオプションがあります。

  1. より多くの消費者を追加します。このオプションを選択すると、DBが複数の同時プロセスによって操作されるようになることに注意してください。 DBが余分な圧力に耐えられることを確認してください。
  2. [〜#〜] qos [〜#〜]消費チャネルの値を増やします。これにより、キューからより多くのメッセージがプルされ、コンシューマでバッファリングされます。これにより、全体の処理時間が長くなります。 5つのメッセージがバッファリングされている場合、5番目のメッセージの完了には、メッセージ1 ... 5の処理時間がかかります。

「コンシューマ部分でマルチスレッドを使用して消費率を上げる必要がありますか?」

適切に設計されたソリューションがない限り、そうではありません。アプリケーションに並列処理を追加すると、コンシューマー側に多くのオーバーヘッドが追加されます。 ThreadPoolを使い果たしたり、メモリ使用量を抑制したりする場合があります。

AMQPを扱う場合、最適なソリューションを設計するには、各プロセスのビジネス要件を考慮する必要があります。受信メッセージはどのくらいの時間に敏感ですか?それらはDB ASAPに永続化する必要がありますか、それともそのデータがすぐに利用可能かどうかはユーザーにとって重要ですか?

データをすぐに保持する必要がない場合は、アプリケーションを変更して、コンシューマーがメッセージをキューから削除し、Redisなどのキャッシュされたコレクションに保存するだけで済みます。キャッシュされたメッセージを順番に読み取って処理する2番目のプロセスを導入します。これにより、キューの長さがフロー制御になるほど大きくならないことが保証されます。また、通常、読み取りリクエストよりもコストが高い書き込みリクエストでDBが攻撃されるのを防ぎます。コンシューマーはメッセージをキューから削除するだけで、後で別のプロセスで処理されます。

15
Paul Mooney

それは本当ですが、より多くのコンシューマーを追加すると、物事がスピードアップする可能性があり、実際の問題はデータベースに保存されます。

ここには、コンシューマ(スレッド、またはマシン)の追加とQoSの変更についての回答がたくさんありますので、ここでは繰り返し説明しません。代わりに、 Aggregator パターンを使用してメッセージをメッセージのグループに集約し、グループを一括してデータベースに一括挿入することを真剣に検討する必要があります。

各メッセージの現在のコードは、おそらく接続を開き、データを挿入し、その接続を閉じます(またはプールに戻ります)。さらに悪いことに、それはトランザクションを使用しているかもしれません。

アグリゲーターパターンを使用することで、フラッシュする前にデータを本質的にバッファリングします。

現在、優れたアグリゲーターを書くのは難しいです。バッファリングの方法を決定する必要があります(つまり、各ワーカーには独自のバッファまたはRedisのような中央バッファがあります)。 Spring統合にはアグリゲーターがいると思います。

2
Adam Gent

パフォーマンスを向上させる方法はたくさんあります。

  1. より多くのプロデューサーでワーカーキューを作成できます。このようにして、単純な負荷分散システムを作成します。 exchange --->キューは使用せず、キューのみを使用してください。この投稿を読む RabbitMQ Non-Round Robin Dispatching

  2. メッセージが表示されたら、データベースにデータを挿入するためのプールスレッドを作成できますが、この場合は失敗を管理する必要があります。

しかし、主な問題はデータベースであり、RabbitMQではありません。優れたチューニング、マルチスレッド、およびワーカーキューを使用すると、スケーラブルで高速なソリューションを実現できます。

お知らせ下さい

2
Gabriele

「それでは、コンシューマのスループットをスピードアップして、コンシューマがプロデューサに追いつき、キューでのメッセージオーバーフローを回避できるようにするにはどうすればよいでしょうか。」これは、「複数のコンシューマを使用して着信メッセージを同時に消費する」という答えであり、マルチスレッドを使用して並行して実行し、これらのコンシューマは原則共有なしを実装します http://www.eaipatterns.com/CompetingConsumers.html =

1
voutrin

回答として、私は両方を提案します。

複数のレシーバーを使用することや、各レシーバーを個別のスレッドでタスクを実行するように設定することで、レシーバーがキュー内の次のメッセージを受け入れることができるようになります。

もちろん、このアプローチは、各操作の結果(私が正しく理解していれば、dbへの書き込み)が他のメッセージからの応答における後続の操作の結果になんら影響を及ぼさないことを前提としています。

0
mbera