在多线程环境中使用Spring WebClient的正确方法

avwztpqn  于 2023-08-02  发布在  Spring
关注(0)|答案(2)|浏览(206)

我有一个关于Spring FrameworkWebClient的问题
在我的应用程序中,我需要做许多类似的API调用,有时我需要更改调用中的头(身份验证令牌)。所以问题来了,这两个选择中哪一个更好:
1.要为所有传入MyService.class的请求创建一个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在其中如何工作,以及它期望如何使用。

  • 谢谢-谢谢
mrfwxfqh

mrfwxfqh1#

这里关于WebClient的两个关键问题:
1.它的HTTP资源(连接、缓存等)由底层库管理,由ClientHttpConnector引用,您可以在WebClient上配置

  1. WebClient是不可变的
    考虑到这一点,您应该尝试在应用程序中重用相同的ClientHttpConnector,因为这将共享连接池-这可以说是性能最重要的事情。这意味着您应该尝试从同一个WebClient.create()调用派生所有WebClient示例。Spring Boot通过为您创建和配置一个WebClient.Builder bean来帮助您实现这一点,您可以将其注入应用的任何位置。
    因为WebClient * 是 * 不可变的所以线程安全的WebClient用于React式环境,在这种环境中,没有任何东西与特定线程绑定(这并不意味着您不能在传统的Servlet应用程序中使用)。
    如果您想更改请求的方式,有几种方法可以实现:

在构建器阶段进行配置

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(hdrs -> {hdrs.add("token", "secret");})
                                 .build();

2ic8powd

2ic8powd2#

根据我的经验,如果您在无法控制的服务器上调用外部API,则根本不要使用WebClient,或者在关闭池机制的情况下使用它。连接池带来的任何性能增益都被内置在(默认reactor-netty)库中的假设所严重地超过,当远程主机突然终止另一个API调用时,这些假设将导致一个API调用的随机错误。在某些情况下,您甚至不知道错误发生在哪里,因为调用都是从共享工作线程进行的。
我犯了使用WebClient的错误,因为RestTemplate的文档说它将来会被弃用。事后看来,我会使用常规的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()来获取全局资源。您可以手动提供池设置,但考虑到即使使用默认设置也会出现错误,我认为不值得冒这个险。

相关问题