spring webclient: повторить попытку с отсрочкой при конкретной ошибке
Я хотел бы повторить запрос 3 раза после ожидания 10 секунд, когда ответ 5xx. но я не вижу метода, который можно использовать. На объекте
WebClient.builder()
.baseUrl("...").build().post()
.retrieve().bodyToMono(...)
я вижу методы:
повторная попытка при условии со счетчиком повторных попыток, но без задержки
.retry(3, {it is WebClientResponseException && it.statusCode.is5xxServerError} )
повторная попытка с отсрочкой и количество раз, но без условия
.retryBackoff
есть также retryWhen
но я не уверен, как это использовать
7 ответов
С реактором-extra вы можете сделать это так:
.retryWhen(Retry.onlyIf(this::is5xxServerError)
.fixedBackoff(Duration.ofSeconds(10))
.retryMax(3))
private boolean is5xxServerError(RetryContext<Object> retryContext) {
return retryContext.exception() instanceof WebClientResponseException &&
((WebClientResponseException) retryContext.exception()).getStatusCode().is5xxServerError();
}
Вы можете сделать это следующим образом:
- Использовать
exchange()
чтобы получить ответ без исключения, а затем выдать конкретное (настраиваемое) исключение в ответе 5xx (это отличается отretrieve()
который всегда будет бросатьWebClientResponseException
либо с4xx
или5xx
статус); - Перехватите это конкретное исключение в логике повтора;
- Использовать
reactor-extra
- в нем есть удобный способ использованияretryWhen()
для более сложных и конкретных попыток. Затем вы можете указать случайную повторную попытку отсрочки передачи, которая начинается через 10 секунд, увеличивается до произвольного времени и повторяется максимум 3 раза. (Или, конечно, вы можете использовать другие доступные методы, чтобы выбрать другую стратегию.)
Например:
//...webclient
.exchange()
.flatMap(clientResponse -> {
if (clientResponse.statusCode().is5xxServerError()) {
return Mono.error(new ServerErrorException());
} else {
//Any further processing
}
}).retryWhen(
Retry.anyOf(ServerErrorException.class)
.randomBackoff(Duration.ofSeconds(10), Duration.ofHours(1))
.maxRetries(3)
)
);
Я полагаю, что retryWhen с Retry.anyOf и Retry.onlyIf устарели. Я нашел этот подход полезным, и он позволяет нам обрабатывать и генерировать исключение, определяемое пользователем.
например:
retryWhen(Retry.backoff(3, Duration.of(2, ChronoUnit.SECONDS))
.filter(error -> error instanceof UserDefinedException/AnyOtherException)
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) ->
new UserDefinedException(retrySignal.failure().getMessage())))
// ...
.retryWhen(
backoff(maxAttempts, minBackoff)
.filter(throwable -> ((WebClientResponseException) throwable).getStatusCode().is5xxServerError()))
// ...
Добавление только withThrowable в ваш существующий код может заставить его работать. Это сработало для меня. Вы можете попробовать что-то вроде этого:
Например :
.retryWhen(withThrowable(Retry.any()
.doOnRetry(e -> log
.debug("Retrying to data for {} due to exception: {}", employeeId, e.exception().getMessage()))
.retryMax(config.getServices().getRetryAttempts())
.backoff(Backoff.fixed(Duration.ofSeconds(config.getServices().getRetryBackoffSeconds())))))
Классreactor.retry.Retry
отreactor-extra
устарел и его следует избегать. Использоватьreactor.util.Retry
класс изreactor-core
.
Существует удобный способ сопоставить статусы ответов с конкретными исключениями, используя методonStatus
метод.
Итак, если вы хотите повторить попытку с кодами состояния 5xx, простым решением будет бросить код 5xx и повторять попытку только при возникновении исключения.CustomException
.
// Retry only on 5xx
webClient.post()
.retrieve()
.onStatus(HttpStatusCode::is5xxClientError, clientResponse -> Mono.error(new CustomException()))
.bodyToMono(...)
.retryWhen(Retry.max(3).filter(t -> t instanceof CustomException))
// Alternatively if you don't want to fail/retry on certain status code you can also return an empty `Mono` to ignore the error and propagate the response
webClient.post()
.retrieve()
.onStatus(httpStatusCode -> httpStatusCode.value() == 404, clientResponse -> Mono.empty())
.bodyToMono(...)
.retryWhen(Retry.max(3).filter(t -> t instanceof CustomException))
вот как я это делаю:
.retryWhen(retryBackoffSpec())
private RetryBackoffSpec retryBackoffSpec() {
return Retry.backoff(RETRY_ATTEMPTS, Duration.ofSeconds(RETRY_DELAY))
.filter(throwable -> throwable instanceof yourException);
}