首页 > 解决方案 > 具有多个客户端的 Spring webflux 超时

问题描述

我有一个与其他几个服务交互的服务。所以我为他们创建了单独的网络客户端(因为不同的基本路径)。我根据https://docs.spring.io/spring/docs/5.1.6.RELEASE/spring-framework-reference/web-reactive.html#webflux-client-builder-reactor-timeout分别为它们设置了超时但这似乎并不有效。对于其中一项服务,尝试将 ReadTimeout 降低到 2 秒,但服务似乎没有超时(使用的日志logging.level.org.springframework.web.reactive=debug显示请求大约需要 6-7 秒才能完成)。

我正在使用 spring5.1 和 netty 0.8 ,但我正在使用 webclient 阻塞,因为我们还没有完全使用 webflux。我试着玩弄每个调用的超时,似乎有些调用确实响应超时,而另一些则没有(更多细节在下面的代码旁边)

我如何初始化网络客户端 -

@Bean
public WebClient serviceAWebClient(@Value("${serviceA.basepath}") String basePath,
                                          @Value("${serviceA.connection.timeout}") int connectionTimeout,
                                          @Value("${serviceA.read.timeout}") int readTimeout,
                                          @Value("${serviceA.write.timeout}") int writeTimeout) {

    return getWebClientWithTimeout(basePath, connectionTimeout, readTimeout, writeTimeout);
}

@Bean
public WebClient serviceBWebClient(@Value("${serviceB.basepath}") String basePath,
                                           @Value("${serviceB.connection.timeout}") int connectionTimeout,
                                           @Value("${serviceB.read.timeout}") int readTimeout,
                                           @Value("${serviceB.write.timeout}") int writeTimeout) {

    return getWebClientWithTimeout(basePath, connectionTimeout, readTimeout, writeTimeout);
}

@Bean
public WebClient serviceCWebClient(@Value("${serviceC.basepath}") String basePath,
                                           @Value("${serviceC.connection.timeout}") int connectionTimeout,
                                           @Value("${serviceC.read.timeout}") int readTimeout,
                                           @Value("${serviceC.write.timeout}") int writeTimeout) {

    return getWebClientWithTimeout(basePath, connectionTimeout, readTimeout, writeTimeout);
}

private WebClient getWebClientWithTimeout(String basePath,
                                          int connectionTimeout,
                                          int readTimeout,
                                          int writeTimeout) {


    TcpClient tcpClient = TcpClient.create()
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout)
            .doOnConnected(connection ->
                    connection.addHandlerLast(new ReadTimeoutHandler(readTimeout))
                            .addHandlerLast(new WriteTimeoutHandler(writeTimeout)));

    return WebClient.builder().baseUrl(basePath)
            .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient))).build();

我本质上是如何使用它的(每个网络客户端都有包装类) -

Mono<ResponseA> serviceACallMono = ..;
Mono<ResponseB> serviceBCallMono = ..;
Mono.zip(serviceACallMono,serviceBCallMono,
(serviceAResponse, serviceBResponse) -> serviceC.getImportantData(serviceAResponse,serviceBResponse))
.flatMap(Function.identity)
.block();

所以在上面,我注意到以下 -

如果我降低 serviceA ReadTimeout ,我会收到超时错误。

如果我降低 serviceB ReadTimeout ,我会收到超时错误。

如果我降低 serviceC ReadTimeout ,它不会响应降低 ReadTimeout。它只是继续工作,直到得到响应。

那么,我在这里遗漏了什么吗?我的印象是这些超时应该适用于所有场景。如果我可以添加更多内容,请告诉我。

编辑:更新,所以我可以以更简单的方式重现这个问题。所以,对于像 -

return serviceACallMono
                .flatMap(notUsed -> serviceBCallMono);

serviceACallMono 的超时是值得的,但是无论你为 serviceB 降低多少它都不会超时。

如果你只是翻转订单 -

return serviceBCallMono
                .flatMap(notUsed -> serviceACallMono);

现在 serviceB 的超时是兑现的,但 serviceA 的超时不是。

我更新了服务以返回 Mono,同时观察此编辑中的行为。

编辑 2:这本质上是 ServiceC#getImportantData 中发生的事情 -

@Override
    public Mono<ServiceCResponse> getImportantData(ServiceAResponse requestA,
                                                   ServiceBResponse requestB) {

        return serviceCWebClient.post()
                .uri(GET_IMPORTANT_DATA_PATH, requestB.getAccountId())
                .body(BodyInserters.fromObject(formRequest(requestA)))
                .retrieve()
                .bodyToMono(ServiceC.class);
    }

formRequest 是一种简单的 POJO 转换方法。

标签: springspring-webfluxreactor-netty

解决方案


我正在使用 spring-boot starter parent 来拉取各种 spring 依赖项。使它从版本 2.1.2 到 2.1.4 似乎解决了这个问题。


推荐阅读