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);
    }
Другие вопросы по тегам