この記事では、障害調査手法、コンカレンシー、Azure Service Bus Java クライアント ライブラリの資格情報の種類に関する一般的なエラー、およびこれらのエラーを解決するための軽減手順について説明します。
ログ記録を有効にして構成する
Azure SDK for Java では、アプリケーション エラーのトラブルシューティングに役立ち、解決を迅速化するために役立つ一貫したログ記録のストーリーが提供されます。 生成されたログは、ルートの問題の特定に役立つターミナル状態に達する前に、アプリケーションのフローをキャプチャします。 ログ記録のガイダンスについては、「Azure SDK for Java でのログ記録の構成」および「トラブルシューティングの概要を参照してください。
ログ記録を有効にするだけでなく、ログ レベルを VERBOSE
または DEBUG
に設定すると、ライブラリの状態に関する分析情報が得られます。 次のセクションでは、詳細ログが有効になっている場合の過剰なメッセージを減らすための log4j2 と logback の構成の例を示します。
Log4J 2 の構成
Log4J 2 を構成するには、次の手順に従います。
- 「Log4j2 に必要な依存関係」セクションの ログサンプル pom.xmlの依存関係を使用して、pom.xml に依存関係を追加します。
- log4j2.xml を src/main/resources フォルダーに追加します。
Logbackを設定する
ログバックを構成するには、次の手順に従います。
- のログサンプル pom.xmlの依存関係を使用して、pom.xml の [Dependencies required for logback]\(ログバックに必要な依存関係\) セクションに依存関係を追加します。
- logback.xml を src/main/resources フォルダーに追加します。
AMQP トランスポート ログを有効にする
問題を診断するのにクライアント ログを有効にするだけでは不十分な場合は、基になる AMQP ライブラリ (Qpid Proton-Jjava.util.logging
が使用されます。 ログ記録を有効にするには、次のセクションに示す内容を含む構成ファイルを作成します。 または、proton.trace.level=ALL
と、java.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 つの方法は、詳細度を変更する方法です。 もう 1 つの方法は、com.azure.messaging.servicebus
や com.azure.core.amqp
などのロガー名パッケージからログを除外するフィルターを追加することです。 例については、「Log4J 2 の構成」セクション および「logback の構成」セクション の XML ファイルを参照してください。
バグを送信すると、次のパッケージのクラスからのログ メッセージが興味深いものになります。
com.azure.core.amqp.implementation
com.azure.core.amqp.implementation.handler
- 例外は、
onDelivery
のReceiveLinkHandler
メッセージを無視できることです。
- 例外は、
com.azure.messaging.servicebus.implementation
ServiceBusProcessorClient でのコンカレンシー
ServiceBusProcessorClient
を使用すると、アプリケーションはメッセージ ハンドラーへの呼び出しの数を同時に構成できます。 この構成により、複数のメッセージを並列で処理できます。 非セッション エンティティからのメッセージを使用する ServiceBusProcessorClient
の場合、アプリケーションは、maxConcurrentCalls
API を使用して目的のコンカレンシーを構成できます。 セッションが有効なエンティティの場合、必要なコンカレンシーは maxConcurrentSessions
倍の maxConcurrentCalls
です。
アプリケーションで、構成されたコンカレンシーよりもメッセージ ハンドラーへの同時呼び出しの数が少ない場合は、スレッド プールのサイズが適切に設定されていない可能性があります。
ServiceBusProcessorClient
は、Reactor グローバル boundedElastic スレッド プールのデーモン スレッドを使用してメッセージ ハンドラーを呼び出します。 このプール内の同時実行スレッドの最大数は、上限によって制限されます。 既定では、この上限は使用可能な CPU コアの 10 倍です。
ServiceBusProcessorClient
がアプリケーションの目的のコンカレンシー (maxConcurrentCalls
または maxConcurrentSessions
時間 maxConcurrentCalls
) を効果的にサポートするには、必要なコンカレンシーよりも高い boundedElastic
プールの上限値が必要です。 既定の上限をオーバーライドするには、システム プロパティを reactor.schedulers.defaultBoundedElasticSize
設定します。
スレッド プールと CPU 割り当ては、ケース バイ ケースで調整する必要があります。 ただし、プールの上限をオーバーライドする場合は、開始点として、同時実行スレッドを CPU コアあたり約 20 から 30 に制限します。
ServiceBusProcessorClient
インスタンスごとに必要なコンカレンシーを約 20 から 30 に上限にすることをお勧めします。 特定のユース ケースをプロファイリングして測定し、それに応じてコンカレンシーの側面を調整します。 負荷の高いシナリオでは、新しい ServiceBusProcessorClient
インスタンスから各インスタンスがビルドされる複数の ServiceBusClientBuilder
インスタンスを実行することを検討してください。 また、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 に解決可能な、または Service Bus にトラフィックをルーティングするように構成された、アプリケーションによって提供される HTTPS エンドポイント アドレスを参照します。 Azure Application Gateway を使用すると、Service Bus にトラフィックを転送する HTTPS フロントエンドを簡単に作成できます。 Service Bus に接続するためのカスタム エンドポイントとして Application Gateway フロントエンド IP アドレスを使用するように、アプリケーション用に Service Bus SDK を構成できます。
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 つのシナリオで発生する可能性があります。
- アプリケーションで、同じ接続を共有する受信側クライアントが多すぎます。 詳細については、「接続共有のボトルネック」セクションを参照してください。
- アプリケーションは、大きな
ServiceBusReceiverClient.receiveMessages
またはServiceBusProcessorClient
値を持つmaxMessages
またはmaxConcurrentCalls
を構成しました。 詳細については、「ServiceBusProcessorClient でのコンカレンシー」セクションを参照してください。
ロック損失エラーの可能性を高める一般的なアプリケーション パターンには、実行時間の長いロック更新タスク (たとえば、数時間にわたるタスク) のスケジュールが含まれます。 前述のように、Service Bus クライアントの制御外のさまざまな要因がロックの更新の成功を妨げる可能性があるため、アプリケーションの設計では、長期間にわたって保証された更新を想定しないようにする必要があります。 実行時間の長い操作の再処理を避けるためには、作業をより小さなチャンクに分割するか、べき等性のあるチェックポイントの処理ロジックを実装することを検討してください。
クライアントのロック更新タスクの数は、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
で失敗します。
次の手順
この記事のトラブルシューティング ガイダンスが、Azure SDK for Java クライアント ライブラリを使用するときに問題を解決するのに役立たない場合は、