У меня есть сервис, который взаимодействует с парой других сервисов.Поэтому я создал для них отдельные веб-клиенты (из-за разных базовых путей).Я установил тайм-ауты для них индивидуально на основе https://docs.spring.io/spring/docs/5.1.6.RELEASE/spring-framework-reference/web-reactive.html#webflux-client-builder-reactor-timeout, но это, похоже, не работает эффективно.Для одной из служб попытался уменьшить время чтения до 2 секунд, но служба, по-видимому, не отключилась (журналы, использующие logging.level.org.springframework.web.reactive=debug
, показывают, что выполнение запроса занимает около 6-7 секунд).
Я использую spring5.1 и netty 0.8, но я использую блокировку с веб-клиентом, потому что мы еще не пошли в олл-ин с 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, он не истекает.
И если вы просто перевернетеorder -
return serviceBCallMono
.flatMap(notUsed -> serviceACallMono);
Теперь тайм-аут для serviceB соблюден, а для serviceA - нет.
Я обновил службу, чтобы она также возвращала Mono, наблюдая за поведением в этом Edit.
Edit 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.