DUICUO

注意!KafkaとRabbitMQを誤用しないでください…

経験豊富なマイクロサービス システム アーキテクトとして、RabbitMQ と Kafka のどちらを選択すべきかとよく尋ねられます。

[[320896]]

Pexelsからの画像

様々な理由から、多くの開発者はこれら2つのテクノロジーを同等のものと見なしています。確かに、シナリオによってはRabbitMQとKafkaのどちらを選択してもほとんど違いがない場合もありますが、これら2つのテクノロジーは基盤となる実装において多くの違いがあります。

シナリオによって必要なソリューションは異なり、間違ったソリューションを選択すると、ソフトウェアの設計、開発、保守の能力に重大な影響を与える可能性があります。

前回の記事では、RabbitMQとApache Kafkaの内部実装に関連する概念を紹介しました。この記事では、これら2つのテクノロジーの違いを、2つの視点から考察します。1つは両者の重要な違い、もう1つはソフトウェアアーキテクトと開発者が認識しておくべき違いです。

まず、これら 2 つの手法を使用して実装しようとしているアーキテクチャ パターンについて説明し、次にどちらをいつ使用するかを評価します。

注 1: RabbitMQ と Kafka の内部構造に詳しくない場合は、私の最初の記事「正直なところ、RabbitMQ と Kafka のどちらを選ぶべきですか?」を読むことを強くお勧めします。

よくわからない場合は、タイトルとチャートをざっと見て、少なくとも違いの概要を把握してください。

注2:前回の記事の公開後、Apache Pulsarについての意見を求める読者が何人かいらっしゃいました。Pulsarは、RabbitMQとKafkaの両方の利点の一部を提供することを目的とした、別のタイプのメッセージングシステムです。

最新のメッセージング システムとしては有望に見えますが、他のプラットフォーム システムと同様に、独自の長所と短所があります。

この記事では主にRabbitMQとKafkaを比較します。後ほどApache Pulsarとの比較も行います。

RabbitMQとKafkaの大きな違い

RabbitMQはメッセージブローカーであり、Apache Kafkaは分散ストリーミングシステムです。セマンティクスから見ると違いは明らかですが、両者の内部的な特性が、様々なユースケースを効果的に設計する能力に影響を与える可能性があります。

たとえば、Kafka はストリーミング データ処理に最適ですが、RabbitMQ ではストリーミング方式でメッセージの順序を維持するのが困難です。

一方、RabbitMQ には再試行ロジックとデッドレター交換が組み込まれていますが、Kafka ではこれらの実装ロジックをユーザーに任せています。

このセクションでは、主にさまざまなシステム間の主な違いについて説明します。

メッセージの順序

RabbitMQは、キューやエクスチェンジに送信されるメッセージの順序を保証しません。プロデューサーから送信されたメッセージをコンシューマーが順番に処理するのは理にかなっているように思えるかもしれませんが、これは非常に誤解を招きます。

RabbitMQ ドキュメントには、メッセージ順序の保証に関する情報が含まれています。

「チャネルに公開されたメッセージは、エクスチェンジ、キュー、および出力チャネルを使用して配信され、最終的には送信された順序で受信されます。」

—RabbitMQブローカーセマンティクス

つまり、単一のコンシューマーである限り、受信するメッセージは順序どおりに処理されます。しかし、複数のコンシューマーが同じキューからメッセージを読み取る場合、メッセージの処理順序は保証されません。

コンシューマーはメッセージを読み取った後にキューに戻す(または再送信する)ことがあるため(処理失敗の場合など)、メッセージの順序は保証されません。

メッセージがキューに戻されると、別のコンシューマーは、戻されたメッセージの後のメッセージをすでに処理していたとしても、そのメッセージの処理を続行できます。

したがって、コンシューマー グループは、次の表に示すように、メッセージを順序どおりに処理しません。

RabbitMQを使用してメッセージの順序を失う例

もちろん、同時コンシューマーの数を 1 に制限することで、RabbitMQ でのメッセージの順序付けを確保できます。

より正確には、並列メッセージ処理によって順序が乱れる問題が発生するため、単一のコンシューマー内のスレッド数を 1 に制限します。

しかし、システムが拡大するにつれて、シングルスレッドのコンシューマーパターンはメッセージ処理能力に深刻な影響を与える可能性があります。したがって、このアプローチを軽々しく選択すべきではありません。

一方、Kafka はメッセージ処理において信頼性の高い順序保証を提供します。Kafka は、同じトピックパーティションに送信されたすべてのメッセージが正しい順序で処理されることを保証します。

最初の記事で紹介したように、デフォルトでは、Kafka はラウンドロビン パーティショナーを使用してメッセージを適切なパーティションに配置します。

ただし、プロデューサーは各メッセージにパーティション キーを設定して、論理データ フロー (同じデバイスからのメッセージや同じテナントに属するメッセージなど) を作成できます。

同じストリームからのすべてのメッセージは同じパーティションに配置され、コンシューマー グループが順番に処理できるようになります。

ただし、同じコンシューマーグループ内では、各パーティションは単一のコンシューマーの単一のスレッドによって処理されることにも注意が必要です。そのため、単一のパーティションの処理能力をスケールすることはできません。

ただし、Kafka では、トピック内のパーティションの数を拡張して、各パーティションで処理するメッセージを少なくし、追加のパーティションを処理するためにコンシューマーを追加することができます。

勝者: メッセージの順序通りの処理を保証するKafkaが勝者であることは明らかです。RabbitMQはこの点では比較的劣っています。

メッセージルーティング

RabbitMQは、定義されたサブスクライバールーティングルールに基づいて、メッセージ交換上のサブスクライバーにメッセージをルーティングできます。トピック交換では、routing_keyと呼ばれる特定のヘッダーを使用してメッセージをルーティングできます。

あるいは、ヘッダー交換は任意のメッセージヘッダーに基づいてメッセージをルーティングできます。どちらのタイプの交換でも、コンシューマーは関心のあるメッセージの種類を効率的に設定できるため、ソリューションアーキテクトに大きな柔軟性がもたらされます。

一方、Kafkaでは、コンシューマーがトピック内のメッセージを処理前にフィルタリングすることはできません。サブスクライブされたコンシューマーは、例外的な状況がない限り、パーティション内のすべてのメッセージを受け入れます。

開発者として、トピックからメッセージを読み取り、フィルタリングし、フィルタリングされたメッセージをコンシューマーがサブスクライブできる別のトピックにプッシュするKafkaストリーミングジョブを使用することもできます。ただし、これにはより多くの作業とメンテナンスが必要になり、メッセージの移動も増加します。

勝者: RabbitMQ は、メッセージのルーティングとフィルタリングをより適切にサポートします。

メッセージのタイミング

RabbitMQ には、メッセージがキューに送信されるのにかかる時間を決定するための機能がいくつか用意されています。

① メッセージの有効期間(TTL)

RabbitMQ に送信される各メッセージには、TTL 属性を関連付けることができます。パブリッシャーは、TTL を直接設定することも、キューの戦略に従って設定することもできます。

システムは、設定されたTTLに基づいてメッセージの有効期間を制限できます。コンシューマーが想定時間内にメッセージを処理しない場合、メッセージは自動的にキューから削除されます(デッドレターエクスチェンジに移動され、後続のメッセージも同様に処理されます)。

TTL は、時間に敏感なコマンドの場合に特に役立ちます。これらのコマンドは、一定期間内に処理されないと意味を失ってしまうためです。

② 遅延/スケジュールメッセージ

RabbitMQは、プラグインを通じて遅延またはスケジュールされたメッセージをサポートします。メッセージ交換でこのプラグインを有効にすると、プロデューサーはRabbitMQにメッセージを送信し、その後、プロデューサーはRabbitMQがメッセージをコンシューマーキューにルーティングするまでの時間を遅延させることができます。

この機能により、開発者は将来のコマンド(それ以前に処理されるべきではないコマンド)をスケジュールできます。

たとえば、プロデューサーがレート制限ルールに遭遇した場合、これらの特定のコマンドの実行を後で延期することがあります。

Kafka はこれらの機能を提供していません。メッセージが到着するとすぐにパーティションに書き込まれるため、コンシューマーはすぐにメッセージにアクセスして処理できます。

Kafka はメッセージに TTL メカニズムを提供していませんが、アプリケーション層でそれを実装できます。

ただし、Kafka パーティションは追記専用のトランザクションログであることを覚えておく必要があります。そのため、メッセージのタイムスタンプ(またはパーティション内の位置)を処理できません。

勝者: この実装は本質的に Kafka を制限するため、間違いなく RabbitMQ が勝者です。

メッセージの保持

コンシューマーがメッセージを正常にコンシュームすると、RabbitMQは対応するメッセージをストレージから削除します。この動作は変更できません。これは、ほぼすべてのメッセージブローカー設計において不可欠な要素です。

対照的に、Kafka はトピックごとにタイムアウトを設定し、タイムアウト期間に達していないメッセージは保持されます。

メッセージの保持に関しては、Kafka はそれを単なるメッセージ ログとして扱い、コンシューマーの消費ステータスは考慮しません。

コンシューマーは各メッセージを無制限に使用でき、パーティション オフセットを操作してこれらのメッセージを「時間内に」ラウンドトリップで処理できます。

Kafka はパーティション内のメッセージの保持時間を定期的にチェックし、設定された保持時間を超えるとメッセージを削除します。

Kafka のパフォーマンスはストレージサイズに依存しません。したがって、理論上は、メッセージの保存はパフォーマンスにほとんど影響を与えません(ノードにこれらのパーティションを保存するのに十分なスペースがある限り)。

勝者:Kafkaは当初からメッセージ保存を目的として設計されていましたが、RabbitMQはそうではありませんでした。したがって、比較の余地はなく、Kafkaが勝者です。

フォールトトレランス

メッセージ、キュー、イベントを扱う際、開発者はメッセージ処理が常に成功すると想定しがちです。結局のところ、プロデューサーが各メッセージをキューまたはトピックに配置すると、コンシューマーがメッセージの処理に失敗しても、成功するまで再試行するだけで済みます。

このアプローチは一見正しいように見えますが、その妥当性を再考する必要があります。まず、特定のシナリオではメッセージ処理が失敗する可能性があることを認識する必要があります。

したがって、解決の一部に人間の介入が必要な状況でも、これらの状況を適切に処理する必要があります。

メッセージ処理では、次の 2 つの障害が発生する可能性があります。

  • 一時的な障害:これらの障害は、ネットワーク接続の問題、CPU負荷の問題、サービスのクラッシュなど、一時的な問題によって発生します。これらの障害は、何度も試行することで軽減できます。
  • 永続的な障害: これらの障害は、追加の再試行では解決できない永続的な問題が原因で発生します。一般的な原因としては、ソフトウェアのバグや無効なメッセージ形式(破損した(有害な)メッセージなど)が挙げられます。

設計者や開発者として、私たちは自分自身に問いかける必要があります。「メッセージ処理の失敗を何回再試行する必要があるか。再試行の間隔はどれくらいにする必要があるか。一時的な障害と永続的な障害をどのように区別するか。」

最も重要なのは、「すべての再試行が失敗した場合、または永続的な障害が発生した場合は、どうすればよいでしょうか?」ということです。

もちろん、ビジネス分野が異なれば答えは異なりますが、メッセージング システムは通常、ソリューションを自分たちで実装するためのツールを提供します。

RabbitMQ は、メッセージ処理の失敗を処理するために、配信の再試行やデッドレター交換 (DLX) などの機能を提供します。

DLX の主なアイデアは、適切な構成情報に基づいてルーティング障害メッセージを DLX に自動的に送信し、異常な再試行、再試行回数、および「人間の介入」のためにキューに送信するなどのルールに従ってスイッチ上でさらに処理することです。

RabbitMQで再試行を処理するための可能なパターンに関する追加の視点を提供するこの記事[1]を参照してください。

RabbitMQ で覚えておくべき最も重要なことは、コンシューマーがメッセージを処理または再試行している間 (キューに返す前であっても)、他のコンシューマーがそのメッセージの後に他のメッセージを同時に処理できるということです。

コンシューマーがメッセージの処理を再試行すると、全体的なメッセージ処理ロジックはブロックされません。

したがって、コンシューマーは、どれだけ時間がかかっても、システム全体の動作に影響を与えることなく、メッセージの処理を同期的に再試行できます。

コンシューマー 1 はメッセージ 1 の処理を​​再試行し続けますが、他のコンシューマーは他のメッセージの処理を続行できます。

RabbitMQとは異なり、Kafkaではこのすぐに使えるメカニズムは提供されていません。Kafkaでは、アプリケーション層でメッセージ再試行メカニズムを独自に提供・実装する必要があります。

さらに、コンシューマーが特定のメッセージを同期的に処理している場合、同じパーティション上の他のメッセージは処理できないことに注意することが重要です。

コンシューマーはメッセージの順序を変更できないため、特定のメッセージを拒否したり再試行したりすることも、そのメッセージに続くメッセージをコミットすることもできません。パーティショニングは単に追加専用のログであることを覚えておいてください。

アプリケーション層ソリューションでは、失敗メッセージを「再試行トピック」に送信し、そのトピックからの再試行を処理できますが、これによりメッセージの順序が失われます。

Uberのエンジニアが実装した例はUber.comでご覧いただけます。メッセージ処理のレイテンシが問題にならない場合は、十分なエラー監視機能を備えたKafkaソリューションで十分でしょう。

コンシューマーがメッセージの再試行で行き詰まると、下部のパーティション内のメッセージは処理されません。

勝者: この問題を解決するためのすぐに使用できるメカニズムを提供するため、RabbitMQ が勝者です。

ストレッチ

RabbitMQ と Kafka のパフォーマンスを確認するためのベンチマークが複数あります。

一般的なベンチマークでは特定の状況で制限がある場合もありますが、Kafka は一般に RabbitMQ に比べてパフォーマンスが優れていると考えられています。

KafkaはシーケンシャルディスクI/Oを使用してパフォーマンスを向上させます。Kafkaのパーティションアーキテクチャは、水平スケーリングではRabbitMQよりも優れていますが、垂直スケーリングではRabbitMQの方が優れています。

大規模な Kafka の展開では、通常、1 秒あたり数十万、または数百万のメッセージを処理できます。

過去にPivotalは1秒あたり100万メッセージを処理するKafkaクラスタの例を文書化しました[2]。しかし、それは30ノードのクラスタで実行され、メッセージの負荷は複数のキューと交換に分散されるように最適化されていました。

典型的なRabbitMQのデプロイメントは3~7ノードのクラスタで構成され、これらのクラスタでは負荷を複数のキューに分散させる必要はありません。これらの典型的なクラスタでは、通常、1秒あたり数万件のメッセージを処理できると予想されます。

勝者: どちらのメッセージング プラットフォームも大きな負荷を処理できますが、Kafka の方がスケーリングに優れており、RabbitMQ よりも高いスループットを実現できるため、このラウンドでは Kafka が勝利します。

ただし、ほとんどのシステムはまだこれらの限界に達していないことに注意してください。つまり、数百万人のユーザーを抱える次世代の超人気ソフトウェアシステムを構築するのでない限り、どちらのメッセージングプラットフォームも問題なく動作するため、スケーラビリティについてあまり心配する必要はありません。

消費者の複雑さ

RabbitMQはスマートブローカーとシンプルなコンシューマーパターンを採用しています。コンシューマーはコンシューマーキューに登録し、RabbitMQは受信したメッセージをコンシューマーキューにプッシュします。RabbitMQにはプルAPIも用意されていますが、あまり使用されていません。

RabbitMQはメッセージの配信とキューからの削除を管理します(メッセージをDLXに移動することもあります)。コンシューマーはこの点について心配する必要はありません。

RabbitMQ の設計によれば、負荷が増加すると、システムを変更することなく、キュー上のコンシューマー グループを 1 つのコンシューマーから複数のコンシューマーに効果的に拡張できます。

RabbitMQの効率的なスケーリング

対照的に、Kafka はシンプルなブローカーとインテリジェントなコンシューマーパターンを採用しています。コンシューマーグループ内のコンシューマーは、トピックパーティションのリースを調整する必要があります(特定のパーティションをリッスンするコンシューマーがコンシューマーグループ内の 1 つのコンシューマーのみとなるようにするためです)。

コンシューマーはパーティションオフセットインデックスの管理と保存も行う必要があります。幸いなことに、Kafka SDK は既にこれをラップしているので、自分で管理する必要はありません。

さらに、負荷が低い場合、単一のコンシューマーが複数のパーティションを並行して処理および管理する必要があり、コンシューマー側でより多くのリソースが消費されます。

もちろん、負荷が増加するにつれて、コンシューマーグループをスケーリングし、コンシューマーの数がトピック内のパーティション数と等しくなるようにするだけで済みます。そのためには、Kafka にパーティションを追加するように設定する必要があります。

しかし、負荷が再び減少したため、以前に追加したパーティションを削除できなくなり、消費者にさらなる作業が必要になりました。

ただし、上で述べたように、Kafka SDK はすでにこの追加作業を実行しています。

Kafka パーティションは削除できません。スケールダウンすると、コンシューマーはより多くの作業を実行する必要があります。

勝者:RabbitMQは設計上、ユーザーフレンドリーで誰でも簡単に操作できるユーザー向けに構築されています。したがって、このラウンドではRabbitMQが勝利します。

どうやって選ぶの?

ここで、私たちは 100 万ドルの価値がある質問に直面しています。「RabbitMQ をいつ使用し、Kafka をいつ使用すればよいのか?」上記の違いをまとめると、次のような結論を簡単に導き出すことができます。

RabbitMQ の優先順位付けの基準:

  • 高度で柔軟なルーティングルール
  • メッセージシーケンス制御(メッセージの有効期限またはメッセージの遅延の制御)
  • コンシューマーがメッセージ (一時的または永続的) の処理に失敗する可能性が高いシナリオにおける高度なフォールト トレランス機能。
  • よりシンプルな消費者実現

Kafka の優先順位付けの基準:

  • 厳密なメッセージ順序
  • 過去のメッセージを再生する可能性を含め、メッセージの保持時間を延長します。
  • 従来のソリューションでは満たせない高い拡張性

ほとんどの場合、どちらのメッセージングプラットフォームも当社の要件を満たすことができます。ただし、最適なツールを選択するのは当社のアーキテクト次第です。

決定を下す際には、上で強調した機能上の違いと非機能上の制限を考慮する必要があります。

これらの制限は次のとおりです。

  • 開発者によるこれら2つのメッセージングプラットフォームの現在の理解
  • マネージドクラウドソリューションの可用性(該当する場合)
  • 各ソリューションの運用コスト
  • 対象スタックのSDKの可用性

複雑なソフトウェア システムを開発する場合、必要なすべてのメッセージング ユース ケースを実装するために同じメッセージング プラットフォームを使用したいと思うことがあります。

しかし、私の経験では、両方のメッセージング プラットフォームを同時に使用すると、通常はより多くのメリットが得られます。

たとえば、イベント駆動型アーキテクチャ システムでは、RabbitMQ を使用してサービス間でコマンドを送信し、Kafka を使用してビジネス イベント通知を実装できます。

その理由は、イベント通知はイベント トレース、バッチ操作 (ETL スタイル)、または監査の目的でよく使用されるため、Kafka のメッセージ保持機能は非常に価値があるからです。

逆に、コマンドは通常、コンシューマー側で追加の処理を必要としますが、この処理は失敗する可能性があるため、高度なフォールト トレランス機能が必要になります。

RabbitMQは機能面で多くの優れた点があります。後ほど詳細な記事を書く予定ですが、ご留意ください。適合性は具体的なニーズによって異なるため、実際の使用感は異なる場合があります。

要約

多くの開発者が RabbitMQ と Kafka を同等のものとみなしていることに気づいたため、これら 2 つの記事を書きました。

これら 2 つの記事が、これら 2 つのテクノロジの実装とそれらの技術的な違いについての深い理解を得るのに役立つことを願っています。

逆に、両者の違いは、それぞれのユースケースにより適したサービスを提供するために、両プラットフォームに影響を及ぼす可能性があります。どちらのメッセージングプラットフォームも優れており、複数のユースケースに優れたサービスを提供しています。

しかし、ソリューション アーキテクトとして、各ユース ケースの要件を理解し、最適化した上で、最も適切なソリューションを選択することが重要です。

関連リンク:

  • https://engineering.nanit.com/rabbitmq-retries-the-full-story-ca4cc6c5b493
  • https://content.pivotal.io/blog/rabbitmq-hits-one-million-messages-per-second-on-google-compute-engine