一般的なRedis Pub/Sub設定問題のトラブルシューティング
Redis Pub/Subの設定課題をマスターして、信頼性の高いリアルタイムメッセージングを確保しましょう。このガイドでは、不安定性の最大の原因である低速コンシューマをトラブルシューティングするための実践的な手順を、重要な`client-output-buffer-limit`ディレクティブを使用して提供します。`CLIENT LIST`コマンドを使用したメモリスパイクの診断方法、専用のサブスクライバ接続の管理方法、およびシステムの整合性を維持するための高ボリュームPub/Sub分離のベストプラクティスの実装方法を学びます。
一般的なRedis Pub/Sub設定問題のトラブルシューティング
Redis Pub/Subは非常にシンプルなため、チームはしばしばそれを運用する必要のない小さなメッセージブローカーのように扱います。パブリッシャーがPUBLISHを呼び出し、サブスクライバがメッセージを受信し、すべてが瞬時に行われるように感じられます。
問題が発生するのは、実際のネットワーク、低速なクライアント、再接続、共有Redisインスタンスが関与する場合です。Pub/Subにはメッセージ履歴、確認応答、再試行キューがありません。これはライブブロードキャストメカニズムです。サブスクライバが切断されている場合、そのサブスクライバにとってメッセージは失われます。サブスクライバが接続されているが十分な速度で読み取れない場合、Redisは設定された制限に達して切断されるまで、そのクライアントの保留中の出力を保持する必要があります。
そのため、Redis Pub/Subはキャッシュ無効化、プレゼンス更新、ライブダッシュボード、および「最新値」通知に最適です。すべてのメッセージを正確に1回処理する必要がある場合や、障害後に再生する必要があるワークフローには適していません。そのような場合は、Redis Streams、RabbitMQ、Kafka、または配信追跡機能を備えた他のブローカーを検討してください。
症状:Pub/Subトラフィックが多いときにメモリが増加する
最も一般的なPub/Subの障害は、低速なサブスクライバです。パブリッシャーは問題ありません。Redisも最初は問題ありません。1つのコンシューマが、プロセスが一時停止している、ネットワークが遅い、またはメッセージハンドラ内で負荷の高い処理を行っているために遅れています。Redisはそのクライアントの出力バッファにメッセージを書き込み続けます。バッファが有用な制限なしに成長することを許されると、メモリプレッシャーが1つの不良サブスクライバからインスタンス全体に広がります。
クライアントを確認します:
redis-cli CLIENT LIST
Pub/Subコマンドと大きな出力メモリを持つクライアントを探します:
id=88 addr=10.0.4.12:51244 flags=P db=0 sub=3 psub=0 omem=13421772 obl=128 oll=64 cmd=subscribe
有用なフィールド:
flags=PはPub/Subクライアントを示します。subとpsubはチャンネルとパターンサブスクリプションの数を示します。omemは出力バッファメモリ(バイト単位)です。oblとollは出力バッファバックログの詳細を示します。cmd=subscribeまたはcmd=psubscribeはクライアントが何をしているかを確認します。
サブスクライバのidleについては慌てないでください。Pub/Sub接続は、コマンドの観点からはアイドル状態であっても、プッシュされたメッセージを受信し続けることがあります。
出力バッファ制限で低速コンシューマを修正する
RedisはPub/Subクライアントに独自の出力バッファ制限クラスを提供します:
client-output-buffer-limit pubsub 32mb 8mb 60
これは次のように解釈します:
- Pub/Subクライアントの出力バッファが32 MBに達した場合、直ちに切断します。
- 8 MBを超えた状態が60秒間続いた場合、切断します。
正確な数値は、トラフィックとクライアントの動作に合わせる必要があります。よくある間違いは、テスト中に切断が煩わしいために制限を0 0 0に設定することです。これにより安全策が取り除かれます。本番環境では、1つのスタックしたサブスクライバがRedisが不安定になるまでメモリを消費させるよりも、切断の方が通常は優れています。
redis.confを変更した後、デプロイメントプロセスに従ってリロードまたは再起動します。ライブの値を検査することもできます:
redis-cli CONFIG GET client-output-buffer-limit
マネージドRedisサービスを使用している場合、この設定はプロバイダーのパラメータグループまたは設定UIを介して公開されている可能性があり、直接ファイルを編集する必要はありません。
症状:サブスクライバが接続しても何も受信しない
最も簡単なテストから始めます。1つのターミナルを開きます:
redis-cli SUBSCRIBE test.channel
別のターミナルを開きます:
redis-cli PUBLISH test.channel hello
Redisホストからは機能するがアプリケーションからは機能しない場合、おそらくクライアント側またはネットワークの問題です。どこでも機能しない場合は、認証、ACL、クラスタルーティング、および実際のチャンネル名を確認します。
チャンネル名は正確なバイト文字列です。events.user、events:User、events:userは異なるチャンネルです。大規模なシステムでは、手動で入力した文字列をサービス全体に散らばらせるよりも、定数または小さなチャンネル命名モジュールを使用することを好みます。
パターンサブスクリプションはさらに混乱の原因を追加します:
PSUBSCRIBE events:*
これはevents:user.createdには一致しますが、prod:events:user.createdには一致しません。アプリケーションが環境名をプレフィックスとして付けている場合は、パターンにプレフィックスを含めます。
認証とACLの問題
最近のRedisデプロイメントでは、1つの共有requirepassパスワードの代わりにACLユーザーを使用することがよくあります。Pub/Subクライアントは、接続、認証、チャンネルパターンへのサブスクライブ、場合によってはパブリッシュのための権限が必要です。
簡単なCLIチェック:
redis-cli -u redis://app-user:[email protected]:6379 PING
redis-cli -u redis://app-user:[email protected]:6379 SUBSCRIBE events:test
一般的な認証症状:
NOAUTH Authentication required:クライアントが認証前にコマンドを送信しました。WRONGPASS invalid username-password pair:パスワードが間違っている、ユーザーが間違っている、またはローテーションされたシークレットがすべての場所にデプロイされていません。NOPERM this user has no permissions:ユーザーは認証されましたが、コマンドまたはチャンネルの権限が不足しています。
ACLでは、チャンネル権限はキー権限とは別です。ユーザーは通常のキーコマンドを実行することは許可されているが、期待するチャンネルにサブスクライブすることは許可されていない場合があります。設定されたユーザーを確認します:
redis-cli ACL GETUSER app-user
テスト中に本番環境のシークレットをシェル履歴に貼り付けないでください。可能な場合は環境変数または一時的な資格情報を使用します。
Pub/Sub接続を通常のコマンドと共有しない
接続がサブスクライブモードに入ると、それはもはや通常のRedisコマンド接続ではありません。プッシュされたメッセージを受信し、サブスクリプション関連のコマンド(SUBSCRIBE、UNSUBSCRIBE、PSUBSCRIBE、PUNSUBSCRIBE、PING、RESET、またはクライアントとRedisのバージョンに応じた終了スタイルのコマンド)のみを発行できます。
一般的なプールから接続を借用してSUBSCRIBEを呼び出すと、そのプールされた接続は事実上、GET、SET、またはその他の通常のコマンドに使用できなくなります。その結果、プールがサブスクライブモードでスタックした接続でゆっくりと満たされるため、ランダムなアプリケーションタイムアウトのように見えることがあります。
別々の接続を使用します:
- 通常のRedisコマンド用の1つの通常のクライアントまたはプール。
- サブスクライバプロセスまたはスレッドごとに1つの長期接続サブスクライバ接続。
- クライアントライブラリが推奨する場合は、1つの別個のパブリッシャー接続。
多くのクライアントライブラリは、この理由でduplicate()またはnew connectionメソッドを提供しています。それを使用してください。プールがうまく処理してくれると推測しないでください。
再接続動作が重要
Pub/Subは再接続後に失われたメッセージを再生しません。サブスクライバが30秒間切断された場合、その間に公開されたすべてのメッセージはそのサブスクライバによって失われます。
これは、アプリケーションにフォールバックがある場合にのみ、キャッシュ無効化に受け入れられます。たとえば、無効化イベントを見逃したサービスは、TTL、バージョンチェック、またはソースデータベースから再構築する方法をまだ持っている必要があります。フォールバックがないと、1回のネットワークの瞬断で、古いローカルキャッシュエントリが長期間残る可能性があります。
再接続時に、クライアントがすべてのチャンネルとパターンに再サブスクライブすることを確認します。一部のライブラリはこれを自動的に行います。その他は、再接続後にハンドラを再登録する必要があります。サブスクライバ接続を強制終了してこれをテストします:
redis-cli CLIENT LIST | grep subscribe
redis-cli CLIENT KILL ID <client-id>
次に、テストメッセージを公開し、アプリケーションが再接続後にそれを受信することを確認します。
高いファンアウトはCPUとネットワークのプレッシャーになる可能性がある
PUBLISHの作業は、一致するサブスクライバとパターンの数に応じて増加します。数千のサブスクライバがいるチャンネルは、Redisがメッセージを数千回プッシュする必要があることを意味します。それでも問題ないかもしれませんが、無料ではありません。
監視します:
redis-cli INFO stats
redis-cli PUBSUB NUMSUB events:test
redis-cli PUBSUB NUMPAT
PUBSUB NUMSUBは、チャンネルに想定した数のサブスクライバがいるかどうかを確認するのに役立ちます。PUBSUB NUMPATは、広範なパターンサブスクリプションが驚きを与える可能性があるため便利です。PSUBSCRIBE *またはPSUBSCRIBE events:*を使用するいくつかのサービスは、意図したよりもはるかに多くのトラフィックを受信する可能性があります。
Pub/Subトラフィックが重要で量が多い場合は、それを分離します。Pub/Sub専用のRedisインスタンスは、サブスクライバのバッファ問題やブロードキャストスパイクからキャッシュとセッションストアを保護します。専用インスタンスは、Pub/Subに合わせて調整された設定、より短い永続性の考慮事項、およびクライアントとネットワーク出力に焦点を当てた監視を持つことができます。
メッセージハンドラを短く保つ
サブスクライバはメッセージを読み取り、最小限の検証を行い、作業を他のものに委譲する必要があります。ハンドラが次のメッセージを読み取る前にデータベースを呼び出したり、HTTPリクエストを行ったり、テンプレートをレンダリングしたり、長時間の計算を実行したりすると、回避しようとしている低速コンシューマになる可能性があります。
より良い形:
Redisメッセージ -> 軽量ハンドラ -> ローカルキュー -> ワーカープール
ローカルキューは、インプロセスキュー、スレッドプール、非同期タスクキュー、または作業がプロセスの再起動後も存続する必要がある場合の耐久性のあるブローカーである可能性があります。重要な考え方は、Redisからの読み取りが最も遅いダウンストリーム依存関係を待つべきではないということです。
作業が信頼性を必要とする場合、Pub/Subは間違ったプリミティブです。コンシューマグループを持つRedis Streamsは、保留中のエントリと確認応答を提供します。デッドレターキュー、遅延再試行、優先順位、または長期保存が必要な場合は、専用のブローカーの方が適している可能性があります。
実用的なトラブルシューティングチェックリスト
Pub/Subが正常でないように見える場合、通常は次の順序で作業します:
SUBSCRIBEとPUBLISHを使用して手動でチャンネルを確認します。CLIENT LISTで高いomemを持つPub/Subクライアントを確認します。client-output-buffer-limit pubsubを検査します。- 認証とACLチャンネル権限を確認します。
- サブスクライバが専用接続を使用していることを確認します。
- サブスクライバ接続を強制終了し、再接続と再サブスクライブの動作を確認します。
- 予期しないファンアウトがないか
PUBSUB NUMSUBとPUBSUB NUMPATを確認します。 - ワークロードが実際にStreamsまたは専用ブローカーを必要とするかどうかを判断します。
Redis Pub/Subは、それが約束することを正確に実行するという意味でのみ信頼性があります:現在接続されているサブスクライバへのライブメッセージブロードキャスト。ほとんどの本番インシデントは、それが耐久性のあるキューのように動作することを期待することから発生します。バッファ制限を設定し、大量のトラフィックを分離し、メッセージが見逃される可能性があるかのようにサブスクライバを設計します。なぜなら、時々実際に見逃されるからです。
インシデント例:1つのダッシュボードがキャッシュを遅くする
通常のキャッシュキーとmetrics:liveと呼ばれる小さなPub/Subチャンネルに使用されるRedisインスタンスを想像してください。最初は、内部ダッシュボードのみがサブスクライブしています。数か月後、いくつかのブラウザゲートウェイプロセスもサブスクライブし、そのうちの1つが低速なWebSocket接続を介して更新を送信し始めます。Redisはダウンストリームのブラウザが遅いことを知りません。それは単に、十分な速度で読み取っていないサブスクライバ接続を見ているだけです。
最初の症状はPub/Subをまったく言及しないかもしれません。通常のGETおよびSET呼び出しを使用するアプリケーションリクエストがタイムアウトし始めます。メモリが上昇します。Redisホストはビジーに見えますが、SLOWLOGは明らかに負荷の高いコマンドを示しません。
有用な手がかりはCLIENT LISTにあります:
flags=P sub=1 omem=25165824 cmd=subscribe
これは、Pub/Subクライアントが大きな出力バッファを持っていることを示しています。設定されたハード制限が32 MBの場合、そのクライアントは切断されようとしています。制限が無効になっている場合、Redisはインスタンス全体がメモリプレッシャー下に置かれるまでバッファリングを続ける可能性があります。
修正は単に制限を引き上げることではありません。引き上げると問題が隠蔽され、次回さらに悪いメモリスパイクを許す可能性があります。より良い対応は次のとおりです:
addr、name、またはクライアントメタデータからサブスクライバプロセスを特定します。- メモリが危険な場合は、最悪のサブスクライバを強制終了します。
client-output-buffer-limit pubsubを追加または厳格化します。- 低速なダウンストリーム配信をRedis読み取りループの外部の内部キューに移動します。
- 高ボリュームのPub/Subを専用のRedisインスタンスに移動することを検討します。
クライアント名を使用すると、これがはるかに簡単になります。多くのRedisライブラリでは、接続名を設定できます。web-1:pubsub-cache-invalidationsやdashboard:metrics-liveなどの名前を使用して、CLIENT LISTがIPとポートのみを表示する代わりに所有者を指すようにします。
CLIENT SETNAME dashboard:metrics-live
この小さな衛生習慣により、漠然としたメモリインシデントが、サブスクライバを所有するチームとの直接的な会話に変わります。