web-dev-qa-db-fra.com

Bonne façon d'utiliser Spring WebClient dans un environnement multi-thread

J'ai une question concernant Spring WebClient

Dans mon application, je dois faire de nombreux appels API similaires, parfois j'ai besoin de changer les en-têtes dans les appels (jeton d'authentification). La question se pose donc, quelle serait la meilleure des deux options:

  1. Pour créer un WebClient pour toutes les demandes entrantes à MyService.class, en le faisant private final champ, comme le code ci-dessous:

    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();
    

Ici se pose une autre question: WebClient est-il sûr pour les threads? (car le service est utilisé par de nombreux threads)

  1. Pour créer un nouveau WebClient pour chaque nouvelle demande entrant dans la classe de service.

Je veux fournir des performances maximales et les utiliser correctement, mais je ne sais pas comment WebClient fonctionne à l'intérieur, ni comment il s'attend à être utilisé.

Je vous remercie.

22
Sergey Luchko

Deux choses clés ici à propos de WebClient:

  1. Ses ressources HTTP (connexions, caches, etc.) sont gérées par la bibliothèque sous-jacente, référencée par le ClientHttpConnector que vous pouvez configurer sur le WebClient
  2. WebClient est immuable

Dans cet esprit, vous devriez essayer de réutiliser le même ClientHttpConnector dans votre application, car cela partagera le pool de connexions - c'est sans doute la chose la plus importante pour les performances. Cela signifie que vous devez essayer de dériver toutes les instances WebClient du même appel WebClient.create(). Spring Boot vous y aide en créant et en configurant pour vous un WebClient.Builder bean que vous pouvez injecter n'importe où dans votre application.

Parce que WebClient est immuable, il est thread-safe. WebClient est destiné à être utilisé dans un environnement réactif, où rien n'est lié à un thread particulier (cela ne signifie pas que vous ne pouvez pas l'utiliser dans une application Servlet traditionnelle).

Si vous souhaitez modifier la façon dont les demandes sont faites, il existe plusieurs façons d'y parvenir:

configurer les choses dans la phase de construction

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

configurer les choses à la demande

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

créer une nouvelle instance client à partir d'une instance existante

// 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

Ne devrait-il pas être:

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

Au lieu

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

?

0
Pavel

D'après mon expérience, si vous appelez une API externe sur un serveur sur lequel vous n'avez aucun contrôle, n'utilisez pas du tout WebClient ou utilisez-le avec le mécanisme de pooling désactivé. Tous les gains de performances du pool de connexions sont largement compensés par les hypothèses intégrées à la bibliothèque (par défaut réacteur-netty) qui provoqueront des erreurs aléatoires sur un appel d'API lorsqu'un autre a été brutalement interrompu par l'hôte distant, etc. Dans certains cas, vous ne '' Je ne sais même pas où l'erreur s'est produite car les appels sont tous effectués à partir d'un thread de travail partagé.

J'ai fait l'erreur d'utiliser WebClient parce que le doc pour RestTemplate a dit qu'il serait obsolète à l'avenir. Avec le recul, j'irais avec HttpClient normal ou Apache Commons HttpClient, mais si vous êtes comme moi et déjà implémenté avec WebClient, vous pouvez désactiver le regroupement en créant votre WebClient comme suit:

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();
}

*** La création d'un WebClient distinct ne signifie pas que WebClient aura un pool de connexions distinct. Regardez simplement le code de HttpClient.create - il appelle HttpResources.get () pour obtenir les ressources globales. Vous pouvez fournir les paramètres du pool manuellement, mais compte tenu des erreurs qui se produisent même avec la configuration par défaut, je ne pense pas que cela en vaille la peine.

0
rougou