効果的なRabbitMQバインディング戦略によるメッセージルーティング
効果的なバインディング戦略でRabbitMQのメッセージルーティングを習得しましょう。このガイドでは、エクスチェンジとキュー間のバインディングの作成と管理方法を説明し、ルーティングキー、ダイレクトおよびトピックエクスチェンジによるパターンマッチング、ファンアウトによるブロードキャスト、ヘッダーによるコンテンツベースのフィルタリングをカバーします。堅牢なメッセージングシステムを構築するための実践的な例とベストプラクティスも含まれています。
効果的なRabbitMQバインディング戦略によるメッセージルーティング
バインディングは、RabbitMQのルーティングが現実のものとなる場所です。プロデューサーはエクスチェンジにパブリッシュし、コンシューマーはキューから読み取り、バインディングはどのキューがどのメッセージを受信するかを決定します。ルーティング設計がクリーンであれば、プロデューサーはコンシューマーの数を把握する必要がなく、コンシューマーはパブリッシャーコードを変更せずに追加できます。設計が乱雑だと、メッセージはルーティング不可能なパスに消え、キューは処理できない作業を受け取り、デプロイのたびに推測ゲームになります。
RabbitMQのバインディング戦略を考える最も有用な方法は、「どのエクスチェンジタイプが最適か?」ではありません。「メッセージ配信についてどのような約束をしているのか?」です。請求イベント、監査ログ、キャッシュ無効化、リトライメッセージはすべて異なるルーティングニーズを持っています。バインディングはその意図を明確にするべきです。
イベントの形状から始める
ルーティングキーは、一時的な実装の詳細ではなく、メッセージに関する安定した事実を記述するときに最も効果的に機能します。orders.createdのようなキーは、アプリケーションのいくつかのバージョンを生き残るでしょう。worker-3.fast-pathのようなキーはおそらく生き残りません。
トピックエクスチェンジでは、小さく一貫した階層を使用します:
domain.entity.action
orders.invoice.created
orders.invoice.paid
orders.shipment.failed
users.account.disabled
コンシューマーは、請求イベントにはorders.invoice.*、すべての注文ドメインイベントにはorders.#、運用障害処理には#.failedにバインドできます。これは、同じエクスチェンジにnew_order、invoice.paid、shipping-errorを混在させるよりもはるかに理解しやすいです。
ダイレクトバインディング:正確なジョブのための正確な名前
ダイレクトエクスチェンジは、パブリッシャーが作業の正確なクラスを知っており、各キューが1つ以上の正確なキーを必要とする場合に適しています。
rabbitmqadmin declare exchange name=orders.events type=direct durable=true
rabbitmqadmin declare queue name=billing.invoice-created durable=true
rabbitmqadmin declare queue name=audit.order-events durable=true
rabbitmqadmin declare binding source=orders.events destination=billing.invoice-created routing_key=invoice.created
rabbitmqadmin declare binding source=orders.events destination=audit.order-events routing_key=invoice.created
rabbitmqadmin declare binding source=orders.events destination=audit.order-events routing_key=invoice.paid
メッセージがinvoice.createdでパブリッシュされた場合、両方のキューがコピーを受信します。shipment.createdでパブリッシュされた場合、別のバインディングが存在しない限り、どちらのキューも受信しません。その正確さがポイントです。
コマンドのような作業キュー、明確なイベント名、小さなルーティングキーのセットにはダイレクトバインディングを使用します。リストが数十のほぼ重複したキーに成長した場合、トピックルーティングの代わりとして使用しないでください。その時点で、手動で脆弱なルーティングテーブルを維持することになります。
トピックバインディング:パブリッシャーを変更せずに柔軟なサブスクリプション
トピックエクスチェンジは、通常、イベントスタイルのシステムにとって最も実用的なデフォルトです。パブリッシャーは1つのルーティングキーを送信します。各キューは、サブスクリプションの広さまたは狭さを決定します。
rabbitmqadmin declare exchange name=platform.events type=topic durable=true
rabbitmqadmin declare queue name=fraud.orders durable=true
rabbitmqadmin declare queue name=ops.failures durable=true
rabbitmqadmin declare queue name=analytics.all-events durable=true
rabbitmqadmin declare binding source=platform.events destination=fraud.orders routing_key=orders.payment.*
rabbitmqadmin declare binding source=platform.events destination=ops.failures routing_key=#.failed
rabbitmqadmin declare binding source=platform.events destination=analytics.all-events routing_key=#
ワイルドカードのルールは正確です:
*はドット間の1つの単語に完全に一致します。#は0個以上の単語に一致します。
したがって、orders.*.failedはorders.payment.failedに一致しますが、orders.eu.payment.failedには一致しません。orders.#はorders、orders.created、orders.eu.payment.failedに一致します。
トピックエクスチェンジの主なリスクは、偶発的な過剰サブスクリプションです。#にバインドされたキューは、エクスチェンジにパブリッシュされたすべてのものを受信します。これは分析やアーカイブシステムには問題ないかもしれませんが、1つのメッセージスキーマしか理解しないサービスにとっては悪い驚きです。広いバインディングはまれにし、それらのキューには正直に名前を付けましょう。
ファンアウトバインディング:ルーティングキーの議論なしでブロードキャスト
ファンアウトエクスチェンジは、すべてのメッセージをバインドされたすべてのキューに送信し、ルーティングキーを無視します。これにより、キャッシュ無効化、ローカル開発のタップキュー、および「すべてのサブシステムがこれを聞くべき」通知に最適です。
rabbitmqadmin declare exchange name=deploy.notifications type=fanout durable=true
rabbitmqadmin declare queue name=slack.deploys durable=true
rabbitmqadmin declare queue name=audit.deploys durable=true
rabbitmqadmin declare binding source=deploy.notifications destination=slack.deploys
rabbitmqadmin declare binding source=deploy.notifications destination=audit.deploys
ルーティングキーが不便だからといってファンアウトを使用しないでください。10のコンシューマーのうち3つだけがメッセージを必要とする場合、ダイレクトまたはトピックルーティングを使用します。ファンアウトは意図的に鈍感です:バインドされたすべてのキューがコピーを取得します。
ヘッダーバインディング:便利ですが、退屈に保つ
ヘッダーズエクスチェンジは、ルーティングキーの代わりにメッセージヘッダーでルーティングします。x-matchバインディング引数を使用して、すべてのヘッダーまたは任意のヘッダーに一致させることができます。
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='documents', exchange_type='headers', durable=True)
channel.queue_declare(queue='pdf-invoices', durable=True)
channel.queue_bind(
exchange='documents',
queue='pdf-invoices',
arguments={'x-match': 'all', 'format': 'pdf', 'type': 'invoice'}
)
channel.basic_publish(
exchange='documents',
routing_key='ignored',
body=b'...',
properties=pika.BasicProperties(headers={'format': 'pdf', 'type': 'invoice'})
)
connection.close()
ヘッダールーティングは、メッセージがすでに意味のあるメタデータを持つシステムから来る場合に便利です。また、不透明になりやすいです。ルーティングルールがキューバインディングからすぐに理解できない場合は、トピックキーを優先します。
実際のインシデントを引き起こすバインディングの間違い
最も一般的なバインディングの失敗は、vhostの不一致です。RabbitMQオブジェクトは仮想ホスト内に存在します。/のorders.eventsというエクスチェンジは、prodのorders.eventsと同じオブジェクトではありません。CLIツールを使用するときは常に-Vまたは-pで確認してください。
rabbitmqctl -p prod list_bindings source_name destination_name routing_key arguments
次の失敗は、ダイレクトエクスチェンジがトピックエクスチェンジのように動作すると仮定することです。orders.*のダイレクトバインディングは、リテラルキーorders.*にのみ一致します。orders.createdには一致しません。ワイルドカードが必要な場合、エクスチェンジタイプはtopicでなければなりません。
もう1つの一般的なものは、キューをリトライエクスチェンジに同じルーティングキーでバインドし、すぐに自分自身に送り返すことです。これにより、高速リトライループが作成される可能性があります。リトライの場合、パスを明示的にします:アクティブキュー -> リトライエクスチェンジ -> 遅延キュー -> 元のエクスチェンジ、最後の試行後にパーキングキューを配置します。
最後に、自動化によって作成された重複バインディングに注意してください。RabbitMQは同じプロパティを持つ重複バインディングを冪等に扱いますが、ほぼ重複は依然として異なります。orders.createdとorder.createdは、誰かがメッセージの半分が間違ったサービスに送られていることに気付くまで、何ヶ月も隣り合って存在することができます。
簡単なレビューチェックリスト
ルーティングの変更をリリースする前に、次の質問に答えるのが好きです:
- パブリッシュを受信するエクスチェンジは何ですか?
- プロデューサーはどの正確なルーティングキーまたはヘッダーを送信しますか?
- どのキューが1つのコピーを受信するべきですか?
- どのキューが絶対に受信してはいけませんか?
- バインディングが一致しない場合はどうなりますか?
- ルーティング不可能なメッセージのための代替エクスチェンジまたはパブリッシャーリターンハンドリングはありますか?
- リトライおよびデッドレターバインディングは一方向ですか、それともループする可能性がありますか?
次に、デプロイされたトポロジーを確認します:
rabbitmqctl -p prod list_exchanges name type durable arguments
rabbitmqctl -p prod list_queues name durable arguments
rabbitmqctl -p prod list_bindings source_name source_kind destination_name destination_kind routing_key arguments
優れたRabbitMQバインディング戦略は通常退屈です。名前は予測可能で、ワイルドカードは意図的で、障害パスは可視です。それはまさに、プロデューサーが真夜中にデプロイし、キューグラフが会議なしで自分自身を説明する必要があるときに望むものです。