一般的なRabbitMQ設定問題のトラブルシューティング

誤った手がかりを追うことなく、RabbitMQのエクスチェンジ、キュー、バインディング、確認応答、権限のミスを見つけて修正します。

一般的なRabbitMQ設定問題のトラブルシューティング

ほとんどのRabbitMQ設定問題は、最初はアプリケーションのバグのように見えます。パブリッシャーはメッセージを送信したと言います。コンシューマーはそれを見たことがないと言います。キューグラフは空か、さらに悪いことに満杯で、誰も理由がわかりません。最速の解決方法は推測をやめて、メッセージの経路(パブリッシャー、エクスチェンジ、バインディング、キュー、コンシューマー、確認応答)を追跡することです。

RabbitMQはトポロジーに厳格です。ダイレクトエクスチェンジはルーティングキーに「ほぼ」一致しません。排他的として宣言されたキューは、共有ワークキューのようには動作しません。必須として公開されたメッセージは返される可能性がありますが、同じルーティング不能なメッセージでもmandatoryなしではエクスチェンジによって単にドロップされる可能性があります。これらの詳細は小さなものですが、午後を無駄にする原因になります。

実際のルートから始める

通常のAMQP公開では、プロデューサーはルーティングキーとともにメッセージをエクスチェンジに送信します。エクスチェンジはそのタイプとバインディングを使用して、どのキューがメッセージを受信すべきかを決定します。その後、コンシューマーはキューから配信をプルし、処理後に確認応答します。

メッセージが消えた場合、4つの質問を自問してください:

  • プロデューサーは、あなたが思っているエクスチェンジと仮想ホストに公開しましたか?
  • そのエクスチェンジは存在し、あなたが思っているタイプですか?
  • そのエクスチェンジから意図したキューへのバインディングはありますか?
  • ルーティングキーは、エクスチェンジタイプに対してそのバインディングと一致していますか?

それは基本的に聞こえますが、多くの実際のインシデントを捕捉します。ステージングと本番では異なる仮想ホストが使用されることがよくあります。デプロイメントスクリプトが一方の環境でorders.createdを宣言し、もう一方でorder.createdを宣言する場合があります。キューがトピックパターンにバインドされていて、余分な単語が1つ欠けている場合があります。

管理UIまたはCLIを使用して、実行中のコードではなく、実際のブローカーを検査します:

rabbitmqctl list_exchanges name type durable auto_delete
rabbitmqctl list_bindings source_name source_kind destination_name destination_kind routing_key
rabbitmqctl list_queues name durable auto_delete exclusive messages_ready messages_unacknowledged consumers

複数の仮想ホストを使用する場合は、-pを含めます:

rabbitmqctl -p production list_bindings source_name destination_name routing_key

ルーティングキーの不一致

ダイレクトエクスチェンジは、バインディングキーの完全一致を必要とします。キューがinvoice.createdでバインドされている場合、invoices.createdで公開されたメッセージは届きません。RabbitMQは複数形、大文字小文字、ドット、ダッシュを修正しません。

トピックエクスチェンジは、1ワードに*、0ワード以上に#を使用します。ワード区切りはドットです。logs.*のバインディングはlogs.infoに一致しますが、logs.app.infoには一致しません。logs.#のバインディングは両方に一致します。

便利なトラブルシューティングのトリックは、広範なバインディングを持つ一時的な診断キューを追加し、既知のテストメッセージを公開することです:

rabbitmqadmin declare queue name=debug.routing durable=false auto_delete=true
rabbitmqadmin declare binding source=events destination=debug.routing destination_type=queue routing_key='#'

これを本番環境で慎重に行い、終了したら診断バインディングを削除してください。目標は、メッセージがエクスチェンジにまったく到達しているかどうかを証明することです。

重要なパブリッシャーの場合は、mandatoryフラグを使用してパブリッシャーリターンを有効にし、ルーティング不能なメッセージがパブリッシャーに表示されるようにします。パブリッシャー確認は、ブローカーが公開を受け入れたことを通知します。リターンは、エクスチェンジがメッセージをどのキューにもルーティングできなかったことを通知します。これらは異なる質問に答えます。

エクスチェンジタイプの間違い

エクスチェンジタイプの変更は、既存のエクスチェンジを異なるプロパティで宣言すると失敗するため、混乱の一般的な原因です。あるサービスがeventstopicとして宣言し、別のサービスがeventsdirectとして宣言した場合、2番目の宣言は前提条件違反になるはずです。

その失敗は良いことです。2つのアプリケーションがルーティングについて黙って意見を異にするのを防ぎます。修正方法は例外をキャッチして無視することではありません。修正方法はトポロジーの所有権を明確にすることです。通常、1つのデプロイメントステップまたはインフラストラクチャモジュールが共有エクスチェンジとキューを宣言し、アプリケーションはプライベートな応答キューを宣言するか、期待されるトポロジーをべき等でアサートするだけにすべきです。

ファンアウトエクスチェンジはルーティングキーを無視します。ヘッダーエクスチェンジはルーティングキーではなくヘッダーでルーティングします。テストメッセージが正しいルーティングキーを持っているが、どのキューも受信しない場合は、すべてのバインディングを編集する前にエクスチェンジタイプを確認してください。

人を驚かせるキュープロパティ

永続的とは、キューの定義がブローカーの再起動後も存続することを意味します。キュー内のすべてのメッセージが存続するわけではありません。メッセージが再起動後も存続するためには、キューが永続的であり、メッセージが永続的として公開されている必要があります。それでも、パブリッシャーはRabbitMQがメッセージを安全に受け入れたことを知る必要がある場合は確認を使用する必要があります。

自動削除キューは、最後のコンシューマーがいなくなった後に削除されます。これらは一時的なサブスクリプションに便利ですが、共有ワークキューには適していません。排他的キューは、それを宣言した接続にスコープされ、その接続が閉じられると消えます。これらは応答キューやプライベートコンシューマーに便利ですが、複数のワーカーインスタンスには適していません。

キューが「ランダムに消える」ように見える場合は、これらのフラグを確認してください:

rabbitmqctl list_queues name durable auto_delete exclusive consumers

また、アプリケーションコードが起動時に既存のキューと異なる引数でキューを宣言していないか確認してください。RabbitMQは、キュータイプ、デッドレターエクスチェンジ、最大長、および一部の耐久性関連設定などのキュー引数を宣言契約の一部として扱います。不一致があると、チャネルが前提条件違反で閉じられる可能性があります。

メッセージは準備完了だが、コンシューマーが何もしない

messages_readyが高く、consumersがゼロの場合、RabbitMQは待機しています。コンシューマーアプリケーションがダウンしているか、間違った仮想ホストに接続しているか、間違ったキュー名を使用しているか、権限によってブロックされている可能性があります。

コンシューマーが接続されているが配信が行われていない場合は、プリフェッチとコンシューマー容量を確認してください:

rabbitmqctl list_consumers queue_name channel_pid consumer_tag ack_required prefetch_count active

手動確認応答と満杯のプリフェッチウィンドウを持つコンシューマーは、既に持っているメッセージの一部を確認応答またはnackするまで、それ以上のメッセージを受信しません。これは、コンシューマーが実際に未確認の作業を保持しているときに、RabbitMQが配信を停止したように見えることがよくあります。

messages_unacknowledgedが高い場合は、コンシューマーログとダウンストリームシステムを確認してください。遅いデータベース、スタックしたHTTP依存関係、または例外をキャッチして確認応答しないハンドラーはすべて、未確認メッセージの壁を作成する可能性があります。

確認応答のバグ

手動確認応答は、信頼性の高い処理のための通常の選択です。コンシューマーは、作業が完了した後にのみ確認応答する必要があります。失敗した場合は、意図的な再キューイングの決定とともに拒否またはnackする必要があります。

危険なパターンは、失敗する可能性のある作業に対してauto_ack=trueを使用することです。自動確認応答では、RabbitMQはメッセージが配信されるとすぐに処理されたと見なします。コンシューマーが受信後にクラッシュした場合、メッセージはキューから消えます。

逆のバグは、決して確認応答しないことです。コンシューマーはメッセージを正常に処理し、おそらくデータベースに書き込みますが、basic_ackを忘れます。RabbitMQはチャネルが閉じられるまで配信を未確認のまま保持し、その後再配信します。これにより、重複作業と増加する未確認カウントが作成されます。

監査が容易なシンプルなハンドラーの形状:

def handle(ch, method, properties, body):
    try:
        process(body)
    except RetryableError:
        ch.basic_nack(method.delivery_tag, requeue=True)
    except Exception:
        ch.basic_nack(method.delivery_tag, requeue=False)
    else:
        ch.basic_ack(method.delivery_tag)

すべての失敗を永遠に再キューイングすると、1つの不良メッセージが無限にループする可能性があります。ポイズンメッセージには、デッドレターエクスチェンジまたは再試行設計を使用してください。

権限と仮想ホスト

RabbitMQの権限は仮想ホストごとにスコープされます。ユーザーは接続できるが、キューまたはエクスチェンジに対する設定、書き込み、または読み取り権限が不足している場合があります。これは、クライアントログでチャネル例外として表示されることがあり、常に親しみやすいアプリケーションエラーとして表示されるわけではありません。

権限を直接確認してください:

rabbitmqctl list_permissions -p production
rabbitmqctl list_user_permissions app_user

公開のみを行うサービスの場合は、必要なエクスチェンジパターンに書き込み権限を付与し、広範な設定権限は避けてください。コンシューマーの場合は、キューに対する読み取り権限と、デッドレターパスへのnackの公開や応答パターンの使用が必要な場合は書き込み権限を付与します。過度に広範な権限は、今日のトラブルシューティングを容易にし、明日のセキュリティレビューを困難にします。

デッドレター設定の間違い

デッドレターエクスチェンジは、失敗を可視化することを目的としています。誤って設定されたデッドレタリングはその逆を行います:メッセージは失敗し、拒否され、バインディングのないエクスチェンジに消えます。

キュー名だけでなく、キュー引数を確認してください:

rabbitmqctl list_queues name arguments

失敗したジョブをデッドレターする必要があるキューの場合、x-dead-letter-exchangeや、場合によってはx-dead-letter-routing-keyなどの引数が表示されるはずです。次に、そのエクスチェンジとそのバインディングをメインルートと同じ方法で検査します。

よくある間違いは、jobs.dlxというデッドレターエクスチェンジを設定するが、デッドレターキューを別のエクスチェンジのjobs.failedにバインドすることです。もう1つは、x-dead-letter-routing-keyを、どのバインディングも一致しない値に設定することです。RabbitMQは、デッドレターメッセージを他の公開と同様にデッドレターエクスチェンジを介してルーティングします。何も一致しない場合、メッセージには有用な行き先がありません。

再試行キューも同じ注意が必要です。TTLとデッドレタリングを使用して再試行を構築する場合は、ルートを紙に描いてください:

main queue -> reject -> retry exchange -> retry queue -> TTL expires -> main exchange -> main queue

次に、すべてのエクスチェンジ、キュー、バインディング、ルーティングキーを確認します。再試行ループは誤って簡単に作成できます。メッセージヘッダーまたはアプリケーション状態に試行回数の上限を設定して、1つの壊れたペイロードが永遠にループしないようにします。

ポリシーの驚き

ポリシーは、アプリケーションコードが言及しなくてもキューの動作を変更できます。ポリシーは、キュータイプ、最大長、TTL、デッドレターエクスチェンジ、またはその他のオプションの引数を設定する場合があります。これは運用に便利ですが、キューがコード宣言と異なる動作をする場合、デバッグを混乱させる可能性があります。

トラブルシューティング中にポリシーをリストします:

rabbitmqctl list_policies

パターンと優先度を確認してください。.*のような広範なポリシーは、後で無関係なチームによって作成されたキューに影響を与える可能性があります。キューが古いメッセージをドロップしている場合は、max-lengthまたはオーバーフロー設定を確認してください。メッセージが予想よりも早く期限切れになる場合は、キューレベルのTTLとメッセージごとの有効期限を確認してください。

アプリケーションが1つの引数セットを宣言し、ポリシーが別のものを適用する場合、RabbitMQのルールは設定によって異なります。一部のオプション引数はポリシーで制御できます。他のものは宣言と一致する必要があります。安全な運用習慣は、キューの動作を1つの明白な場所に保ち、アプリケーションのデフォルトを意図的にオーバーライドするポリシーを文書化することです。

プロデューサーとコンシューマーがトポロジーを宣言する場合

多くのクライアントライブラリは、すべてのサービスが起動時にエクスチェンジ、キュー、バインディングを宣言することを容易にします。これは開発では便利です。本番環境では、所有権の問題を引き起こす可能性があります。

プロデューサーとコンシューマーの両方が同じキューを宣言する場合、すべての重要なプロパティについて同意する必要があります。あるデプロイメントがキューを自動削除から永続的に変更したり、デッドレター引数を変更したりすると、次に起動するサービスが前提条件エラーで失敗する可能性があります。これは黙ったままのドリフトよりはましですが、それでもデプロイを壊す可能性があります。

共有トポロジーの場合は、1つの所有者(Terraform、Ansible、移行ジョブ、または明確に責任のある1つのサービス)を優先してください。アプリケーションの起動は、期待されるトポロジーが存在することをアサートできますが、誰もレビューしていないデフォルトで共有キューを軽率に作成すべきではありません。

プライベートトポロジーは異なります。一時的な応答キューや排他的サブスクリプションキューを作成するサービスは、そのキューを直接所有できます。違いは、別のサービスがキュー名と動作に依存しているかどうかです。

1つの既知の正常な公開パスを維持する

重要なシステムの場合は、小さな診断パブリッシャーまたはランブックコマンドを維持し、期待されるエクスチェンジ、ルーティングキー、仮想ホストを介して無害なメッセージを送信します。実際のアプリケーションと同じ資格情報クラス、または少なくともルーティングとアクセスの問題を捕捉するのに十分近い権限セットを使用する必要があります。

その既知の正常なパスは、デプロイメント中に役立ちます。診断メッセージがルーティングされるがアプリケーションメッセージがルーティングされない場合は、アプリからの実際のルーティングキー、ヘッダー、仮想ホストを比較してください。診断メッセージも失敗する場合、問題はおそらくトポロジー、権限、またはブローカーの状態です。

実用的なインシデントチェックリスト

メッセージが欠落しているかスタックしている場合は、すべてを再起動する前にライブ状態を収集してください:

rabbitmqctl -p production list_queues name messages_ready messages_unacknowledged consumers state
rabbitmqctl -p production list_bindings source_name destination_name routing_key
rabbitmqctl -p production list_exchanges name type durable
rabbitmqctl -p production list_connections name user vhost state

次に、一意のIDを持つ1つの既知のテストメッセージを送信し、ログを通じてトレースします。パスがすでに不明確なランダムな本番メッセージでテストしないでください。

ほとんどのRabbitMQ設定問題は、宣言されたトポロジーをパブリッシャーとコンシューマーの動作と一致させれば、神秘的ではありません。ブローカーは通常、指示されたことを正確に実行しています。作業は、指示されたことがチームの意図と異なる場所を見つけることです。