web-dev-qa-db-ja.com

Spring5のWebClientから返されたFluxのblock()メソッドを使用できますか?

WebClientを使用して通信する2つのアプリケーションを含むSpring Boot 2.0デモアプリケーションを作成しました。また、WebClientの応答からFluxのblock()メソッドを使用すると、通信が停止することがよくあります。何らかの理由でFluxではなくListを使用したい。

サーバーサイドアプリケーションはこんな感じです。 Fluxオブジェクトを返すだけです。

@GetMapping
public Flux<Item> findAll() {
    return Flux.fromIterable(items);
}

そして、クライアント側(またはBFF側)のアプリケーションは次のようになります。サーバーからFluxを取得し、block()メソッドを呼び出してFluxをListに変換します。

@GetMapping
public List<Item> findBlock() {
    return webClient.get()
        .retrieve()
        .bodyToFlux(Item.class)
        .collectList()
        .block(Duration.ofSeconds(10L));
}

最初はうまく機能しますが、findBlock()は応答せず、数回アクセスした後にタイムアウトします。 findBlock()メソッドを変更してFluxを返し、collectList()とblock()を削除すると、うまく機能します。次に、block()メソッドがこの問題の原因であると想定します。
そして、リストを返すようにfindAll()メソッドを変更しても、何も変わりません。

サンプルアプリケーション全体のソースコードはこちらです。
https://github.com/cero-t/webclient-example

「リソース」はサーバーアプリケーションであり、「フロント」はクライアントアプリケーションです。両方のアプリケーションを実行した後、localhost:8080にアクセスすると正常に機能し、いつでも再ロードできますが、localhost:8080/blockにアクセスすると正常に機能しているように見えますが、何度か再ロードすると応答しなくなります。


ちなみに、「spring-boot-starter-web」依存関係を「リソース」アプリケーションではなく「フロント」アプリケーションのpom.xmlに追加すると、Tomcatを使用するため、この問題は発生しません。この問題はNettyサーバーが原因ですか?

どんなガイダンスもいただければ幸いです。

7
Shin Tanimoto

まず、itemsがメモリからフェッチされ、I/Oが関与していない場合にのみ、Flux.fromIterable(items)を使用することをお勧めします。それ以外の場合は、ブロッキングAPIを使用して取得する可能性があります。これにより、リアクティブアプリケーションが破損する可能性があります。この場合、これはメモリ内のリストなので、問題ありません。 Flux.just(item1, item2, item3)を使用することもできます。

以下を使用するのが最も効率的です。

@GetMapping("/")
public Flux<Item> findFlux() {
  return webClient.get()
    .retrieve()
    .bodyToFlux(Item.class);
}

Itemインスタンスは、非常に効率的な方法でオンザフライで読み取り/書き込み、デコード/エンコードされます。

一方、これは推奨される方法ではありません。

@GetMapping("/block")
public List<Item> findBlock() {
  return webClient.get()
    .retrieve()
    .bodyToFlux(Item.class)
    .collectList()
    .block(Duration.ofSeconds(10L));
}

この場合、フロントアプリケーションはcollectListを使用してメモリ全体のアイテムリスト全体をバッファリングしていますが、いくつかのサーバースレッドの1つもブロックしています利用可能です。サーバーがそのデータを待ってブロックされ、同時に他の要求にサービスを提供できないため、これによりパフォーマンスが非常に低下する可能性があります。

この特定のケースでは、アプリケーションが完全に壊れるため、状況はさらに悪化します。コンソールを見ると、次のことがわかります。

WARN 3075 --- [ctor-http-nio-7] io.netty.util.concurrent.DefaultPromise  : An exception was thrown by reactor.ipc.netty.channel.PooledClientContextHandler$$Lambda$532/356589024.operationComplete()

reactor.core.Exceptions$BubblingException: Java.lang.IllegalArgumentException: Channel [id: 0xab15f050, L:/127.0.0.1:59350 - R:localhost/127.0.0.1:8081] was not acquired from this ChannelPool
    at reactor.core.Exceptions.bubble(Exceptions.Java:154) ~[reactor-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]

これはおそらく、0.7.4.RELEASEで修正される reactor-nettyクライアント接続プールの問題 にリンクされています。これの詳細はわかりませんが、HTTP応答がクライアント接続から適切に読み取られないため、接続プール全体が破損しているようです。

spring-boot-starter-webを追加すると、アプリケーションでTomcatが使用されますが、主に、Spring WebFluxアプリケーションがSpring MVCアプリケーションになります(現在、いくつかのリアクティブな戻り値型をサポートしていますが、ランタイムモデルが異なります)。 Tomcatでアプリケーションをテストする場合は、POMにspring-boot-starter-Tomcatを追加できます。これにより、Spring WebFluxでTomcatが使用されます。

6
Brian Clozel