paint-brush
Spring WebFlux アプリケーションをデバッグする方式 に@vladimirf
7,509 測定値
7,509 測定値

Spring WebFlux アプリケーションをデバッグする方法

Vladimir Filipchenko7m2023/05/09
Read on Terminal Reader

長すぎる; 読むには

Spring WebFlux アプリケーションのデバッグは、特に複雑なリアクティブ ストリームを処理する場合、困難な作業になる可能性があります。コードのブロック、同時実行の問題、競合状態などの問題はすべて、診断が難しい微妙なバグを引き起こす可能性があります。根本的な原因は、リアクティブ アプリに慣れている人にとっては明らかです。ただし、以下のいくつかのプラクティスは、修正するのに非常に役立つ場合があります。
featured image - Spring WebFlux アプリケーションをデバッグする方法
Vladimir Filipchenko HackerNoon profile picture
0-item
1-item
Spring WebFlux アプリケーションのデバッグは、特に複雑なリアクティブ ストリームを処理する場合、困難な作業になる也许 性があります。スタック トレースが問題の几乎愿意を明確に示す従来のブロッキング アプリケーションとは異なり、リアクティブなアプリケーションはデバッグが困難になる也许 性があります。コードのブロック、同時実行の問題、競合状態などの問題はすべて、診断が難しい平淡なバグを引き起こす也许 性があります。


シナリオ

バグに対処するとき、それは必ずしもコード関連の問題ではありません。近些年のリファクタリング、チームの変更、厳しい締め切りなど、一連の要因である也许 性があります。実日常生活では、しばらく前に退職し、あなたが入社したばかりの人が制作した大規模なアプリケーションのトラブルシューティングを行うことはよくあることです。


ドメインとテクノロジーについて少し知っていても、生命が楽になるわけではありません。


接下来のコード例では、最近几天チームに到庭した人にとって、バグのあるコードがどのように見えるかを臆想したいと思いました。


このコードのデバッグは、挑戦ではなく旅のように考えてください。本质的な原因分析は、リアクティブ アプリに慣れている人にとっては明らかです。ただし、下面のいくつかのプラクティスは、校准するのに如此に役立つ場合があります。


 @GetMapping("/greeting/{firstName}/{lastName}") public Mono<String> greeting(@PathVariable String firstName, @PathVariable String lastName) { return Flux.fromIterable(Arrays.asList(firstName, lastName)) .filter(this::wasWorkingNiceBeforeRefactoring) .transform(this::senselessTransformation) .collect(Collectors.joining()) .map(names -> "Hello, " + names); } private boolean wasWorkingNiceBeforeRefactoring(String aName) { // We don't want to greet with John, sorry return !aName.equals("John"); } private Flux<String> senselessTransformation(Flux<String> flux) { return flux .single() .flux() .subscribeOn(Schedulers.parallel()); }


つまり、このコードは次のようになります。パラメーターとして提拱される名前の先頭に「Hello」を追加します。あなたの同僚のジョンは、彼のラップトップですべてが動作すると言っています。それは本当だ:


 > curl localhost:8080/greeting/John/Doe > Hello, Doe


しかし、 curl localhost:8080/greeting/Mick/Jaggerのように実行すると、次のスタックトレースが表示されます。


 java.lang.IndexOutOfBoundsException: Source emitted more than one item at reactor.core.publisher.MonoSingle$SingleSubscriber.onNext(MonoSingle.java:134) ~[reactor-core-3.5.5.jar:3.5.5] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): *__checkpoint ⇢ Handler com.example.demo.controller.GreetingController#greeting(String, String) [DispatcherHandler] *__checkpoint ⇢ HTTP GET "/greeting/Mick/Jagger" [ExceptionHandlingWebHandler] Original Stack Trace: <18 internal lines> at java.base/java.util.concurrent.FutureTask.run(FutureTask.java) ~[na:na] (4 internal lines)


いいですね、どちらのトレースも上記のコード サンプルにはつながりません。


明らかになるのは、1) GreetingController#greetingメソッドで発生し、2) クライアントが「HTTP GET "/greeting/Mick/Jagger を実行した」ということだけです。

.doOnError()


刚开始的に試す最も簡単な方案は、グリーティング チェーンの最後に `.doOnError()` コールバックを追加することです。


 @GetMapping("/greeting/{firstName}/{lastName}") public Mono<String> greeting(@PathVariable String firstName, @PathVariable String lastName) { return Flux.fromIterable(Arrays.asList(firstName, lastName)) // <...> .doOnError(e -> logger.error("Error while greeting", e)); }


いい試みですが、ログには减少が見られません。


それにもかかわらず、Reactor の内部组织スタック トレース:



デバッグ中にdoOnError役立つ/役に立たないいくつかの方法を次に示します。

  1. ロギング: doOnErrorを使用してエラー メッセージをログに記録し、リアクティブ ストリームで何が問題になったかについてより多くのコンテキストを提供できます。これは、多数のオペレーターを含む複雑なストリームで問題をデバッグする場合に特に役立ちます。

  2. 回復: doOnError使用して、エラーから回復し、ストリームの処理を続行することもできます。たとえば、 onErrorResume使用して、エラーの場合にフォールバック値またはストリームを提供できます。

  3. デバッグ: ほとんどの場合、 doOnErrorログで既に見たものを除いて、より良いスタック トレースを提供しません。これを優れたトラブルシューティング ツールとして信頼しないでください。


ログ()


次のステップは、以前に追加したdoOnError() log()メソッド呼び出しに置き換えることです。それが得るのと同じくらい簡単です。 log()は、すべての Reactive Streams シグナルを監視し、デフォルトで INFO レベルのログにトレースします。


ログに表示される追加情報を見てみましょう。


どの Reactive メソッドが呼び出されたかを確認できます ( onSubscriberequest 、およびonError )。さらに、これらのメソッドがどのスレッド (プール) から呼び出されたかを知ることは、非常に役立つ情報です。ただし、それは私たちの場合には関係ありません。


スレッドプールについて


スレッド名ctor-http-nio-2 reactor-http-nio-2の略です。リアクティブ メソッドonSubscribe()およびrequest() IO スレッド プール (スケジューラ) で実行されました。これらのタスクは、それらを送信したスレッドですぐに実行されました。


senselessTransformation内に.subscribeOn(Schedulers.parallel())を持つことで、Reactor に別のスレッド プールの追加の要素をサブスクライブするよう指示しました。これが、 parallel-1スレッドでonErrorが実行された理由です。


スレッド プールの詳細については、この記事を参照してください。


log()メソッドを使用すると、ロギング ステートメントをストリームに追加できるため、データの流れの追跡と問題の診断が容易になります。 flatMap、サブチェーン、ブロッキング呼び出しなどでより複雑なデータ フローを使用している場合は、すべてをログに記録することで多くのメリットが得られます。毎日使うのにとても簡単で素敵なものです。しかし、根本的な原因はまだわかっていません。


Hooks.onOperatorDebug()


命令Hooks.onOperatorDebug()は、Reactor に、リアクティブ ストリーム内のすべてのオペレーターに対してデバッグ モードを有効にするよう指示し、より詳細なエラー メッセージとスタック トレースを可能にします。


公式ドキュメントによると:

後でエラーが確認されると、元のアセンブリ ライン スタックの詳細を示す缓和された特殊が追加されます。適切なスタック情報をインターセプトするために、プロデューサー (Flux.map、Mono.fromCallable など) が実際に呼び出される前に呼び出す需要があります。


この下令は、ランタイムごとに 1 回呼び出す重要があります。最適な場所の 1 つは、構成またはメイン クラスです。私たちのユースケースでは、次のようになります。


 public Mono<String> greeting(@PathVariable String firstName, @PathVariable String lastName) { Hooks.onOperatorDebug(); return // <...> }


Hooks.onOperatorDebug()を追加することで、最終的に調査を進めることができます。 Stacktrace の方がはるかに便利です。



42 行目には、 single()呼び出しがあります。


上にスクロールしないでください。senselessTransformation senselessTransformation次のようになります。

 private Flux<String> senselessTransformation(Flux<String> flux) { return flux .single() // line 42 .flux() .subscribeOn(Schedulers.parallel()); }


それが从来原因です。


single()は、Flux ソースから 1 つの項目を発行するか、複数の要素を持つソースに対してIndexOutOfBoundsExceptionを通知します。これは、メソッド内のフラックスが複数のアイテムを発行することを意味します。呼び出し階層を上に行くと、もともとFlux.fromIterable(Arrays.asList(firstName, lastName)) 2 つの要素を持つ Flux があることがわかります。


フィルタリング メソッドwasWorkingNiceBeforeRefactoring項目がJohnと等しい場合にフラックスから項目を削除します。これが、John という名前の大学でコードが機能する理由です。は。


Hooks.onOperatorDebug() 、ストリームがどのように処理されているかについてより詳細な情報を提供するため、複雑なリアクティブ ストリームをデバッグするときに特に役立ちます。ただし、デバッグ モードを有効にすると、アプリケーションのパフォーマンスに影響を与える可能性があるため (スタック トレースが読み込まれるため)、開発およびデバッグ中にのみ使用し、運用環境では使用しないでください。


チェックポイント


Hooks.onOperatorDebug()とほぼ同じ効果を最小限のパフォーマンスへの影響で達成するために、特別なcheckpoint()演算子があります。ストリームのそのセクションのデバッグ モードを有効にしますが、ストリームの残りの部分は影響を受けません。


フィルタリング後と変換後に 2 つのチェックポイントを追加しましょう。


 public Mono<String> greeting(@PathVariable String firstName, @PathVariable String lastName) { return Flux.fromIterable(Arrays.asList(firstName, lastName)) .filter(this::wasWorkingNiceBeforeRefactoring) /* new */ .checkpoint("After filtering") .transform(this::senselessTransformation) /* new */ .checkpoint("After transformation") .collect(Collectors.joining()) .map(names -> "Hello, " + names); }


ログを見てください:


このチェックポイントの内訳は、変換後として説明されている 2 番目のチェックポイントの後にエラーが観察されたことを示しています。実行中に最初のチェックポイントに到達していないという意味ではありません。だったのですが、エラーが出るようになったのは2回目以降です。そのため、 After filtering は表示されません。


とから、内訳で言及されているさらに 2 つのチェックポイントも確認できます。呼び出し階層まで、設定した後に到達しました。


説明の他に、 checkpoint()メソッドの 2 番目のパラメーターとしてtrueを追加することで、Reactor にチェックポイントのスタック トレースを生成させることができます。生成されたスタック トレースによって、チェックポイントのある行が表示されることに注意してください。元の例外のスタックトレースは作成されません。説明を提供することでチェックポイントを簡単に見つけることができるため、あまり意味がありません。


結論


これらのベスト プラクティスに従うことで、デバッグ プロセスを簡素化し、Spring WebFlux アプリケーションの問題をすばやく当前して解決できます。経験豊富な開発者でも、リアクティブ プログラミングを始めたばかりでも、これらのヒントは、コードの品質と信頼性を往上させ、ユーザーにより良いエクスペリエンスを展示 するのに役立ちます。


바카라사이트 바카라사이트 온라인바카라