Ошибка вызова API: не обнаружено ни одного элемента или сигнала терминала в течение 8000 мс (и резервный вариант не настроен)/Spring WebClient/Повторить попытку после тайм-аута

Когда происходит .timeout(Duration.ofSeconds(requestTimeout)) ниже, печатается сообщение

ОШИБКА [parallel-6] com.www.xyz.bbb.restv2retry.RestV2ServiceImpl:888 — не обнаружено ни одного элемента или сигнала терминала в течение 80 000 мс в режиме «retryWhen» (и резервный вариант не настроен).

я получаю сообщение, потому что при вызове API на какой-то сервер произошел тайм-аут, и ответ не пришел, но я хочу повторить попытку, когда наступит тайм-аут, поэтому внутри метода retryPolicy() я использовал условие || throwable.getCause() экземпляр TimeoutException, чтобы повторить попытку, когда наступит тайм-аут, но все еще не работает.

Есть идеи, как повторить попытку после тайм-аута?

Ниже приведен код

@Service
public class RestV2ServiceImpl implements RestV2Service {

    private static final Logger LOGGER = LoggerFactory.getLogger(RestV2ServiceImpl.class);

    @Value("${apirest.server.requestTimeoutInSeconds}")
    private Long requestTimeout;

    @Value("${retryPolicy.maxAttempts}")
    private Integer restV2retryPolicyMaxAttempts;

    @Value("${retryPolicy.periodInMilliseconds}")
    private Long restV2initialInterval;

    @Autowired
    private WebClient webClient;

    @Override
    public String updateRequestV2(String url, String requestJson, String versionSequence) {
        return webClient.patch()
                .uri(url)
                .header(HttpHeaders.CONTENT_TYPE, "application/json-patch+json")
                .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
                .header("If-Match", versionSequence)
                .body(BodyInserters.fromValue(requestJson))
                .retrieve()
                .onStatus(this::retryableStatuses, this::retryableStatusException)
                .bodyToMono(String.class)
                .retryWhen(retryPolicy())
                .timeout(Duration.ofSeconds(requestTimeout))
                .doOnError(throwable -> LOGGER.error(throwable.getMessage()))
                .block();
    }

   
    /**
     * Return the range of HTTP codes for which REST v2 requests should be retried.
     * @param status HttpStatus returned from a REST call
     * @return true if request should be retried
     */
    private boolean retryableStatuses(HttpStatus status) {
        return status.is5xxServerError();
    }

    /**
     * Creates an exception to be analyzed by the "retryWhen(...)" method to indicate
     * conditions for which a request should be retried.
     * @param response HTTP response from the latest REST request
     * @return
     */
    private Mono<? extends Throwable> retryableStatusException(ClientResponse response) {
        return response.bodyToMono(String.class).map(body ->
                new HttpServerErrorException(response.statusCode(), body));
    }

    /**
     * Defines the policy for retrying a REST request.
     * This policy retries on HttpServerErrorException created by the retryableStatusException
     * method (which defines retryable server side errors) and on WebClientRequestException which defines
     * client side errors prior to processing a request (e.g. certain request timeouts).
     * @return the policy
     */
    private Retry retryPolicy() {
        return Retry.backoff(restV2retryPolicyMaxAttempts, Duration.ofMillis(restV2initialInterval))
                    .filter(throwable -> throwable instanceof HttpServerErrorException || throwable instanceof WebClientRequestException || || throwable.getCause() instanceof TimeoutException)
                    .onRetryExhaustedThrow(((retryBackoffSpec, retrySignal) -> new RetryExhaustedException(retrySignal.failure().getMessage())));
    }
}

Необходимо повторить попытку, даже если истекло время ожидания

Порядок операторов имеет значение. Если вы установите тайм-аут после повторной попытки, он будет иметь приоритет над механизмом повторной попытки. Но если вы поставите повторную попытку после, то при наступлении таймаута стратегия повтора получит ошибку таймаута и затем сможет повторить попытку.

amanin 22.05.2024 15:44
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
1
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема решена. Было 2 вещи, которые я изменил.

Спасибо @amanin, да, после изменения порядка, так что сначала

.timeout(Duration.ofSeconds(requestTimeout)) then 
    .retryWhen(retryPolicy())

это собиралось повторить попытку.

throwable.getCause() instanceof TimeoutException - здесь я использовал io.netty.handler.timeout.TimeoutException но на самом деле это было java.util.concurrent.TimeoutException

Работало После изменения условия на

throwable instanceof HttpServerErrorException
                                || throwable instanceof WebClientRequestException
                                || throwable instanceof TimeoutException;

Другие вопросы по теме