spring - 具有多个客户端的 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 转换方法。
解决方案
我正在使用 spring-boot starter parent 来拉取各种 spring 依赖项。使它从版本 2.1.2 到 2.1.4 似乎解决了这个问题。