Spring Webclient:在特定错误上进行退避重试

27

当响应为5xx时,我希望在等待10秒后重试请求3次。但是我没有找到可用的方法。关于对象:

WebClient.builder()
                .baseUrl("...").build().post()
                .retrieve().bodyToMono(...)

我可以看到方法:

在条件上重试,但没有延迟的重试次数

.retry(3, {it is WebClientResponseException && it.statusCode.is5xxServerError} )

采用退避和重试的方式,不设限制次数

.retryBackoff 

还有一个retryWhen,但我不确定如何使用它。

7个回答

43

使用reactor-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();
}

更新:使用新的API,相同的解决方案将是:

    .retryWhen(Retry.fixedDelay(3, Duration.ofSeconds(10))
            .filter(this::is5xxServerError));

//...

private boolean is5xxServerError(Throwable throwable) {
    return throwable instanceof WebClientResponseException &&
            ((WebClientResponseException) throwable).getStatusCode().is5xxServerError();
}

@RobertW.,我以前从未使用过协程。我会尝试在这个周末调查你的问题。 - Alexander Pankin
1
已解决,我会回答自己的问题 https://stackoverflow.com/questions/60827183/spring-webclient-retry-with-webflux-fn-reactor-addons - Robert W.
1
该内容已被弃用。 - im_bhatman

9
你可以采用以下方法:
  • 使用exchange()方法获取响应,而不抛出异常,然后在5xx响应时抛出特定(自定义)异常(这与retrieve()不同,后者始终会抛出具有4xx5xx状态的WebClientResponseException);
  • 在重试逻辑中拦截此特定异常;
  • 使用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() 中,我们可以针对不同的异常使用不同的重试策略吗?比如说,对于某些异常,我们想要更高的 maxRetries 值? - tuan.dinh
retryWhen(reactor.retry.Retry) method is deprecated and to be removed in v3.4. But use retryWhen(reactor.util.retry.Retry) - Sarvar Nishonboyev

8

我假设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())))

3
// ...
.retryWhen(
    backoff(maxAttempts, minBackoff)
        .filter(throwable -> ((WebClientResponseException) throwable).getStatusCode().is5xxServerError()))
// ...

1

只需在现有代码中添加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())))))

0
这是我的做法:
 .retryWhen(retryBackoffSpec())

private RetryBackoffSpec retryBackoffSpec() {
        return Retry.backoff(RETRY_ATTEMPTS, Duration.ofSeconds(RETRY_DELAY))
                .filter(throwable -> throwable instanceof yourException);
    }

0

来自reactor-extra的类reactor.retry.Retry已被弃用,应避免使用。请使用reactor-core中的reactor.util.Retry类。

有一种方便的方法可以将响应状态映射到具体的异常,即使用onStatus方法。

因此,如果您想在5xx状态码上重试,一个简单的解决方案是在5xx上抛出CustomException,并且只有当异常为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))



网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接