Azure Service Bus のトラブルシューティング

この記事では、エラー調査手法、コンカレンシー、Azure Service Bus Java クライアント ライブラリでの資格情報の種類に関する一般的なエラー、これらのエラーを解決する移行手順について説明します。

ログ記録の有効化と構成

Azure SDK for Java では、アプリケーション エラーをトラブルシューティングして、その解決を促進するために、一貫したログ記録が提供されます。 生成されたログにより、最終状態に達する前のアプリケーションのフローが取得され、根本原因を特定するのに役立ちます。 ログ記録のガイダンスについては、「トラブルシューティングの概要」の「Azure SDK for Java でログ記録を構成する」を参照してください。

ログ記録を有効にするだけでなく、ログ レベルを VERBOSE または DEBUG にすることで、ライブラリの状態に分析情報を提供します。 次のセクションでは、詳細ログが有効になっている場合の過剰なメッセージを減らすための log4j2 と logback の構成の例を示します。

Log4J 2 を構成する

Log4J 2 を構成するには次の手順を実行します。

  1. 「Log4J 2 に必要な依存性」セクションの [logging sample pom.xml] からの依存性を使用して pom.xml に依存性を追加します。
  2. log4j2.xmlsrc/main/resources フォルダーに追加します。

Logback を構成する

Logback を構成するには、次の手順を実行します。

  1. 「Logback に必要な依存性」セクションの [logging sample pom.xml] からの依存性を使用して pom.xml に依存性を追加します。
  2. logback.xmlsrc/main/resources フォルダーに追加します。

AMQP トランスポート ログを有効にする

問題を診断するのにクライアント ログ記録を有効にするだけでは、不十分な場合は、基になる AMQP ライブラリである Qpid Proton-J でファイルへのログ記録を有効にできます。 Qpid Proton-J は、 java.util.logging を使用します。 ログ記録を有効にするには、次のセクションに示す内容を含む構成ファイルを作成します。 または、 proton.trace.level=ALLjava.util.logging.Handler 実装に必要な任意の構成オプションを設定します。 実装クラスとそのオプションについては、Java 8 SDK ドキュメントの 「java.util.logging をパッケージ化」 するを参照してください。

AMQP トランスポート フレームをトレースするには、 PN_TRACE_FRM=1 環境変数を設定します。

「logging.properties」サンプル ファイル

次の構成ファイルは、Proton-J からの TRACE レベルの出力を proton-trace.log に記録します。

handlers=java.util.logging.FileHandler
.level=OFF
proton.trace.level=ALL
java.util.logging.FileHandler.level=ALL
java.util.logging.FileHandler.pattern=proton-trace.log
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=[%1$tF %1$tr] %3$s %4$s: %5$s %n

ログ記録の削減

ログ記録を減らす方法の 1 つとして、詳細度の変更が挙げられます。 別の方法として、 com.azure.messaging.servicebuscom.azure.core.amqp などのロガー名パッケージからログを除外するフィルターを追加します。 例については、 「Log4J 2 の構成」 および 「logback の構成」 セクションの XML ファイルを参照してください。

バグを送信すると、次のパッケージのクラスからのログ メッセージが興味を引くものになります。

  • com.azure.core.amqp.implementation
  • com.azure.core.amqp.implementation.handler
    • 例外として、 ReceiveLinkHandleronDelivery メッセージを無視できます。
  • com.azure.messaging.servicebus.implementation

ServiceBusProcessorClient におけるコンカレンシー

ServiceBusProcessorClient を使用すると、アプリケーションはメッセージ ハンドラーへの同時呼び出し数を構成できます。 この構成により、複数のメッセージを並列処理できるようになります。 セッション以外のエンティティからのメッセージを使用する ServiceBusProcessorClient の場合、アプリケーションでは maxConcurrentCalls API を使用して目的のコンカレンシーを構成できます。 セッションが有効なエンティティの場合、必要なコンカレンシーは maxConcurrentSessions x maxConcurrentCalls です。

アプリケーションで、構成されたコンカレンシーよりもメッセージ ハンドラーへの同時呼び出しの数が少ない場合は、スレッド プールのサイズが適切に設定されていない可能性があります。

ServiceBusProcessorClient では、Reactor グローバル boundedElastic スレッド プールのデーモン スレッドを使用してメッセージ ハンドラーを呼び出します。 このプール内の同時実行スレッドの最大数は上限によって制限されています。 デフォルトでは、この上限は使用可能な CPU コアの 10 倍です。 アプリケーションに必要なコンカレンシー (maxConcurrentCalls または maxConcurrentSessions x maxConcurrentCalls) を ServiceBusProcessorClient で効果的にサポートするには、必要なコンカレンシーよりも高いプール上限値 boundedElastic が必要です。 デフォルトの上限は、システム プロパティ reactor.schedulers.defaultBoundedElasticSize を設定することでオーバーライドできます。

スレッド プールと CPU 割り当てはケース バイ ケースで調整する必要があります。 ただし、プールの上限をオーバーライドする場合は、最初に同時実行スレッドを CPU コアあたり約 20 ~ 30 に制限します。 ServiceBusProcessorClient インスタンスごとに必要なコンカレンシーを約 20 ~ 30 に制限することをお勧めします。 特定のユース ケースをプロファイリングして測定し、それに応じてコンカレンシーの側面を調整します。 負荷の高いシナリオでは、各インスタンスが新しい ServiceBusClientBuilder インスタンスからビルドされる複数の ServiceBusProcessorClient インスタンスを実行することを検討してください。 1 つのホストのダウンタイムがメッセージ処理全体に影響しないように、コンテナーや VM などの専用ホストで各 ServiceBusProcessorClient を実行することも検討してください。

CPU コアの少ないホストでプールの上限に高い値を設定すると、悪影響を及ぼす可能性があることに注意してください。 CPU リソースが不足しているか、少ない CPU でスレッドが多すぎるプールの兆候には、頻繁なタイムアウト、ロックの損失、デッドロック、スループットの低下があります。 コンテナーで Java アプリケーションを実行している場合は、2 つ以上の vCPU コアを使用することをお勧めします。 コンテナー化された環境で Java アプリケーションを実行する場合は、1 つの vCPU コア未満を選択することはお勧めしません。 リソース管理に関する詳細な推奨事項については、「Java アプリケーションのコンテナー化」を参照してください。

接続共有のボトルネック

共有 ServiceBusClientBuilder インスタンスから作成されたすべてのクライアントは、Service Bus 名前空間への同じ接続を共有します。

共有接続を使用すると、1 つの接続のクライアント間で多重化操作が可能になりますが、クライアントが多数存在する場合や、クライアント全体の高い負荷が生成される場合にも、共有がボトルネックになる可能性があります。 各接続には、I/O スレッドが関連付けられています。 接続を共有する場合、クライアントはこの共有 I/O スレッドの作業キューに作業を配置します。各クライアントの進行状況は、キューの作業のタイムリーな完了に応じます。 I/O スレッドは、エンキューされた作業を順次処理します。 つまり、共有接続の I/O スレッドの作業キューが多くの処理保留で終了する場合、この症状は低 CPU の場合と類似します。 この条件は、コンカレンシーに関する前の章に説明があります。たとえば、クライアントのストール、タイムアウト、ロックの喪失、回復パスの速度低下などです。

Service Bus SDK は、接続 I/O スレッドの reactor-executor-* 名前付けパターンを使用します。 アプリケーションで共有接続のボトルネックが発生すると、I/O スレッドの CPU 使用率に反映されることがあります。 また、ヒープ ダンプあるいはライブ メモリでは、 ReactorDispatcher$workQueue オブジェクトは I/O スレッドの作業キューです。 ボトルネック期間中のメモリ スナップショット内の長い作業キューは、共有 I/O スレッドが保留中の動作でオーバーロードされていることを示す場合があります。

そのため、Service Bus エンドポイントに対するアプリケーションの負荷が受信メッセージまたはペイロード サイズの全体的な数に関して合理的に高い場合は、ビルドするクライアントごとに個別のビルダー インスタンスを使用する必要があります。 たとえば、エンティティ (キューまたはトピック) ごとに、新しい ServiceBusClientBuilder を作成し、そこからクライアントを構築できます。 特定のエンティティへの負荷が非常に高い場合は、そのエンティティに対して複数のクライアント インスタンスを作成するか、複数のホスト (コンテナや VM など) でクライアントを実行して負荷を分散することができます。

Application Gateway カスタム エンドポイント使用時にクライアントが停止する

カスタム エンドポイント アドレスは、Service Bus に解決可能なアプリケーション提供の HTTPS エンドポイント アドレス、または Service Bus にトラフィックをルーティングするように構成されたアプリケーション提供されるの HTTPS エンドポイント アドレスを参照します。 Azure Application Gateway を使用すると、Service Bus にトラフィックを転送する HTTPS フロントエンドを簡単に作成できます。 アプリケーション用に Service Bus SDK を構成して、Service Bus に接続するためのカスタム エンドポイントとして Application Gateway フロントエンド IP アドレスを使用できます。

Application Gateway には、さまざまな TLS プロトコル バージョンをサポートする複数のセキュリティ ポリシーが用意されています。 TLSv1.2 を最小バージョンとして適用する定義済みのポリシーがあり、最小バージョンとして TLSv1.0 を持つ古いポリシーも存在します。 HTTPS フロントエンドには TLS ポリシーが適用されます。

現時点では、Service Bus SDK は Application Gateway フロントエンドによる特定のリモート TCP 終了を認識しません (最小バージョンとして TLSv1.0 を使用)。 たとえば、フロントエンドが TCP FIN を送信した場合、プロパティが更新されたときに接続を閉じる ACK パケットは SDK で検出されないために再接続されず、クライアントはメッセージを送受信できなくなります。 このような停止は、最小バージョンとして TLSv1.0 を使用する場合にのみ発生します。 これを解消するには、Application Gateway フロントエンドの最小バージョンとして TLSv1.2 以上のセキュリティ ポリシーを使用します。

すべての Azure サービスでの TLSv1.0 と 1.1 のサポートは 2024 年 10 月 31 日に終了することがすでに発表されているため、TLSv1.2 への移行を強くお勧めします。

メッセージまたはセッション ロック喪失

Service Bus キューまたはトピック サブスクリプションには、リソース レベルでロック期間が設定されています。 受信側クライアントがリソースからメッセージをプルすると、Service Bus ブローカーはメッセージに初期ロックを適用します。 最初のロックは、リソース レベルで設定されたロック期間中有効になります。 有効期間終了前にメッセージ ロックを更新しない場合、Service Bus ブローカーはメッセージを解放して、他の受信者が使用できるようにします。 アプリケーションがロックの有効期限後にメッセージを完了または破棄しようとすると、API 呼び出しはエラー com.azure.messaging.servicebus.ServiceBusException: The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue で失敗します。

Service Bus クライアントは、有効期限が切れる前にメッセージ ロックを継続的に更新するバックグラウンド ロック更新タスクの実行をサポートしています。 デフォルト設定では、ロック更新タスクは 5 分間ごとに実行されます。 ServiceBusReceiverClientBuilder.maxAutoLockRenewDuration(Duration) を使用して、ロックの更新期間を調整できます。 Duration.ZERO 値を渡すと、ロック更新タスクは無効になります。

次の一覧では、ロック喪失エラーの原因となる可能性がある使用パターンまたはホスト環境の一部について説明します。

  • ロック更新タスクが無効になり、アプリケーションのメッセージ処理時間がリソース レベルで設定されたロック期間を超えています。

  • アプリケーションのメッセージ処理時間が設定したロック更新タスク期間を超えています。 ロックの更新期間が明示的に設定されていない場合、デフォルト設定は 5 分間であることに注意してください。

  • ServiceBusReceiverClientBuilder.prefetchCount(prefetch) を使用してプリフェッチ値を正の整数に設定することで、アプリケーションがプリフェッチ機能を有効にしました。 プリフェッチ機能が有効になっている場合、クライアントは Service Bus エンティティ (キューまたはトピック) からプリフェッチと同数のメッセージを取得し、メモリ内プリフェッチ バッファーに保存します。 メッセージは、アプリケーションに受信されるまで、プリフェッチ バッファーに保存されます。 メッセージがプリフェッチ バッファー内にある間、クライアントはメッセージのロックを拡張しません。 プリフェッチ バッファーに留まっている間にメッセージ ロックの有効期限が切れるほどアプリケーションの処理に時間がかかる場合、アプリケーションは期限切れのロックでメッセージを取得する可能性があります。 詳細については、「プリフェッチがデフォルト設定ではない理由」を参照してください。

  • ホスト環境には、ネットワークの一時的な障害や停止など、ロック更新タスクがロックを更新できないネットワークの問題が時折発生します。

  • ホスト環境に十分な CPU がないか、間欠的に CPU サイクルが不足すると場合に、ロック更新タスクの実行が期限切れになります。

  • ホスト システムの時刻が正確ではない場合 (クロックがずれているなど)、ロック更新タスクが遅れ、時間内に実行されなくなります。

  • 接続 I/O スレッドがオーバーロードすると、ロック更新ネットワーク呼び出しを時間通り実行する機能に影響があります。 この問題が発生する原因には、次の 2 つのシナリオがあります。

    • アプリケーションで、同じ接続を共有する受信側クライアントが多すぎる。 詳細については、「接続共有のボトルネック」章を参照してください。
    • アプリケーションは、大きな maxMessages 値または maxConcurrentCalls 値を持つように ServiceBusReceiverClient.receiveMessages または ServiceBusProcessorClient を構成している。 詳細については、「ServiceBusProcessorClient のコンカレンシー」章を参照してください。

クライアント内のロック更新タスクの数は、maxMessages または maxConcurrentCalls に設定された ServiceBusProcessorClient または ServiceBusReceiverClient.receiveMessages パラメーター値と同じです。 複数のネットワーク呼び出しを行うロック更新タスクの数が多い場合は、Service Bus 名前空間の調整にも悪影響を及ぼす可能性があります。

ホストに十分なリソースが確保されていない場合、実行中のロック更新タスクが数個しかない場合でも、ロックが喪失することがあります。 コンテナーで Java アプリケーションを実行している場合は、2 つ以上の vCPU コアを使用することをお勧めします。 コンテナー化された環境で Java アプリケーションを実行する場合は、1 つの vCPU コアより少ない数を選択することはお勧めしません。 リソース管理に関する詳細な推奨事項については、「Java アプリケーションのコンテナー化」を参照してください。

ロックに関する同じ詳細は、セッションが有効になっている Service Bus キューまたはトピック サブスクリプションにも関連します。 受信側クライアントがリソース内のセッションに接続すると、ブローカーはセッションに初期ロックを適用します。 セッションのロックを維持するには、クライアントのロック更新タスクが期限切れになる前にセッション ロックを更新し続ける必要があります。 セッションが有効なリソースの場合、基になるパーティションは、Service Bus ノード間で負荷分散を実現するために動くことがあります。たとえば、負荷を共有するために新しいノードが追加された場合などです。 そのような場合、セッション ロックが失われることがあります。 アプリケーションがセッションのロック喪失後にメッセージを完了または破棄しようとすると、API 呼び出しはエラー com.azure.messaging.servicebus.ServiceBusException: The session lock was lost. Request a new session receiver で失敗します。

7.15.x または最新のバージョンにアップグレードする

問題が発生した場合は、まず最新バージョンのService Bus SDKにアップグレードして解決を試みる必要があります。 バージョン7.15.xは、長年のパフォーマンスと信頼性の懸念を解決する大規模な再設計です。

バージョン7.15.x以降では、スレッド ホッピングが削減され、ロックが削除され、ホット パスのコードが最適化され、メモリ割り当てが削減されます。 これらの変更により、ServiceBusProcessorClient のスループットは最大 45 ~ 50 倍向上しました。

バージョン7.15.x以降では、さまざまな信頼性の向上も行われています。 いくつかの競合状態(プリフェッチやクレジット計算など)に対応し、エラー処理も改善されました。 これらの変更により、さまざまなタイプのクライアントで一過性の問題が発生した場合の信頼性が向上しました。

最新のクライアントの使用

バージョン7.15.x以降では、これらの改善が加えられた新しい基盤となるフレームワークは、V2-Stackと呼ばれます。 このリリースラインには、前世代の基盤となるスタック(バージョン7.14.xが使用するスタック)と新しいV2スタックの両方が含まれています。

デフォルトでは、V2-Stackを使用するクライアントもあれば、V2-Stackのオプトインが必要なクライアントもあります。 クライアントを構築する際に com.azure.core.util.Configuration 値を指定することで、クライアントタイプの特定のスタック(V2または前世代)のオプトインまたはオプトアウトを行うことができます。

例えば、 ServiceBusSessionReceiverClient を使ったV2-Stackベースのセッション受信では、以下の例のようにオプトインが必要である:

ServiceBusSessionReceiverClient sessionReceiver = new ServiceBusClientBuilder()
    .connectionString(Config.CONNECTION_STRING)
    .configuration(new com.azure.core.util.ConfigurationBuilder()
        .putProperty("com.azure.messaging.servicebus.session.syncReceive.v2", "true") // 'false' by default, opt-in for V2-Stack.
        .build())
    .sessionReceiver()
    .queueName(Config.QUEUE_NAME)
    .buildClient();

次の表では、クライアントの種類と対応する構成名、およびクライアントが最新バージョン 7.17.0 の V2-Stack を現在デフォルトで使用できるかどうかを示しています。 既定でV2-Stack上にないクライアントの場合は、上記の例を使用してオプトインできます。

クライアントの種類 構成名 デフォルトでV2-Stackを使用していますか?
送信者および管理クライアント com.azure.messaging.servicebus.sendAndManageRules.v2 はい
非セッション・プロセッサーおよびリアクター受信クライアント com.azure.messaging.servicebus.nonSession.asyncReceive.v2 はい
セッション・プロセッサー受信クライアント com.azure.messaging.servicebus.session.processor.asyncReceive.v2 はい
セッション・リアクター受信クライアント com.azure.messaging.servicebus.session.reactor.asyncReceive.v2 はい
非セッション同期受信クライアント com.azure.messaging.servicebus.nonSession.syncReceive.v2 いいえ
セッション同期受信クライアント com.azure.messaging.servicebus.session.syncReceive.v2 いいえ

com.azure.core.util.Configurationを使う代わりに、環境変数やシステム・プロパティを使って同じコンフィギュレーション名を設定することで、オプトインやオプトアウトを行うことができます。

次のステップ

この記事のトラブルシューティング ガイダンスが、Azure SDK for Java クライアント ライブラリを使用するときの問題の解決に役立たない場合は、 Azure SDK for Java GitHub リポジトリ問題を提出する ことをお勧めします。