web-dev-qa-db-ja.com

マルチスレッド環境でSpring WebClientを使用する正しい方法

Spring WebClientについて質問があります。

私のアプリケーションでは、多くの同様のAPI呼び出しを行う必要があります。呼び出しのヘッダーを変更する必要がある場合があります(認証トークン)。したがって、問題が発生します。2つのオプションのどちらが優れているでしょうか。

  1. MyService.classへのすべての受信リクエストに対して1つのWebClientを作成するには、それをprivate finalフィールド、以下のコードのような:

    private final WebClient webClient = WebClient.builder()
            .baseUrl("https://another_Host.com/api/get_inf")
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
            .build();
    

ここで別の質問が発生します:WebClientはスレッドセーフですか? (サービスは多くのスレッドで使用されるため)

  1. サービスクラスに着信する新しいリクエストごとに新しいWebClientを作成します。

最大のパフォーマンスを提供し、正しい方法で使用したいのですが、その中でWebClientがどのように機能するのか、またWebClientがどのように使用されるのかを知りません。

ありがとうございました。

22
Sergey Luchko

WebClientに関する2つの重要な点:

  1. そのHTTPリソース(接続、キャッシュなど)は、ClientHttpConnectorで構成できるWebClientによって参照される、基礎となるライブラリによって管理されます
  2. WebClientは不変です

これを念頭に置いて、アプリケーション全体で同じClientHttpConnectorを再利用するようにしてください。これにより、接続プールが共有されます。これは、おそらくパフォーマンスにとって最も重要なことです。つまり、同じWebClient.create()呼び出しからすべてのWebClientインスタンスを派生させる必要があります。 Spring Bootは、WebClient.Builder Bean。アプリのどこにでも挿入できます。

WebClientは不変であるため、スレッドセーフです。 WebClientは、特定のスレッドに何も関連付けられていないリアクティブ環境で使用するためのものです(これは、従来のサーブレットアプリケーションで使用できないことを意味するものではありません)。

リクエストの作成方法を変更したい場合は、いくつかの方法で実現できます。

ビルダーフェーズで構成する

WebClient baseClient = WebClient.create().baseUrl("https://example.org");

リクエストごとに設定する

Mono<ClientResponse> response = baseClient.get().uri("/resource")
                .header("token", "secret").exchange();

既存のインスタンスから新しいクライアントインスタンスを作成する

// mutate() will *copy* the builder state and create a new one out of it
WebClient authClient = baseClient.mutate()
                .defaultHeaders(headers -> {headers.add("token", "secret");})
                .build();
30
Brian Clozel

それはすべきではありません:

 WebClient wc = WebClient
            .builder()
            .baseUrl(SERER_Origin)
            .build();

代わりに

WebClient wc = WebClient.create().baseUrl("https://example.org");

0
Pavel

私の経験では、制御できないサーバーで外部APIを呼び出す場合は、WebClientをまったく使用しないか、プーリングメカニズムをオフにして使用します。接続プーリングによるパフォーマンスの向上は、(デフォルトのreactor-netty)ライブラリに組み込まれている仮定によって大きく補われます。これにより、あるAPI呼び出しがリモートホストによって突然終了したときに、別のAPI呼び出しでランダムエラーが発生します。場合によっては、呼び出しはすべて共有ワーカースレッドから行われるため、エラーが発生した場所もわかります。

RestTemplateのドキュメントには将来廃止される予定があると記載されているため、WebClientの使用を間違えました。後から考えると、通常のHttpClientまたはApache Commons HttpClientを使用しますが、私と同じで、すでにWebClientで実装されている場合は、次のようにWebClientを作成してプールをオフにすることができます。

private WebClient createWebClient(int timeout) {
    TcpClient tcpClient = TcpClient.newConnection();
    HttpClient httpClient = HttpClient.from(tcpClient)
        .tcpConfiguration(client -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeout * 1000)
            .doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(timeout))));

    return WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(httpClient))
        .build();
}

***別個のWebClientを作成しても、WebClientが別個の接続プールを持つことにはなりません。 HttpClient.createのコードを見てください。HttpResources.get()を呼び出してグローバルリソースを取得します。プール設定を手動で指定することもできますが、デフォルトのセットアップでも発生するエラーを考慮して、リスクに見合う価値があるとは考えていません。

0
rougou