You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring WebClient能否配置多retryWhen处理不同错误响应?

如何为Spring WebClient添加多场景的差异化重试策略

你遇到的问题很典型:连续调用retryWhen()确实会覆盖之前的重试配置,因为每个retryWhen操作会替换上游的重试逻辑,而不是叠加多个规则。要实现针对不同错误场景的差异化重试,我们需要把多个策略合并成一个统一的重试规格,或者通过组合方式实现。

先修正你的异常判断逻辑

首先注意你原来的is5xx只判断了503(ServiceUnavailable),但需求是覆盖502和503,所以先调整Predicate:

protected static final Predicate<Throwable> is401 = throwable -> 
    throwable instanceof WebClientResponseException.Unauthorized;

protected static final Predicate<Throwable> is5xxServerError = throwable -> 
    throwable instanceof WebClientResponseException.ServiceUnavailable || // 503
    (throwable instanceof WebClientResponseException && 
     ((WebClientResponseException) throwable).getStatusCode() == HttpStatus.BAD_GATEWAY); // 502

protected static final Predicate<Throwable> is429 = throwable -> 
    throwable instanceof WebClientResponseException.TooManyRequests;

方案一:构建单一的定制化Retry规格

这种方式适合所有场景共享最大重试次数,且只需要区分延迟和前置操作的情况:

// 构建定制化重试规则
Retry multiScenarioRetry = Retry.custom()
        .maxAttempts(5) // 所有场景最多重试5次,可按需调整
        // 只对我们关心的异常触发重试
        .filter(throwable -> is401.test(throwable) || is5xxServerError.test(throwable) || is429.test(throwable))
        // 根据异常类型返回不同的重试延迟
        .backoff(context -> {
            Throwable failure = context.failure();
            if (is401.test(failure)) {
                return Duration.ofSeconds(0); // 401时立即重试
            } else if (is5xxServerError.test(failure)) {
                return Duration.ofSeconds(5); // 502/503延迟5秒重试
            } else if (is429.test(failure)) {
                return Duration.ofSeconds(20); // 429延迟20秒重试
            }
            return Duration.ofSeconds(0); // 默认 fallback,不会触发
        })
        // 401重试前先执行令牌刷新逻辑
        .doBeforeRetry(retrySignal -> {
            if (is401.test(retrySignal.failure())) {
                // 这里替换成你的令牌刷新逻辑
                // refreshAccessToken();
                System.out.println("Refreshing access token before retrying 401...");
            }
        })
        // 重试耗尽后抛出原异常
        .onRetryExhaustedThrow((spec, signal) -> signal.failure());

然后将这个统一的Retry规格应用到WebClient:

WebClient.create()
        .get()
        .uri("endpointuri")
        .retrieve()
        .bodyToFlux(String.class)
        .retryWhen(multiScenarioRetry);

方案二:组合多个独立的Retry实例

如果每个错误场景需要不同的最大重试次数(比如401只重试2次,5xx重试5次),可以用Retry.from()组合多个独立的Retry规则:

// 分别定义每个场景的重试规则
Retry retry401 = Retry.fixedDelay(2, Duration.ofSeconds(0))
        .filter(is401)
        .doBeforeRetry(signal -> {
            // 401重试前刷新令牌
            // refreshAccessToken();
        })
        .onRetryExhaustedThrow((spec, signal) -> signal.failure());

Retry retry5xx = Retry.fixedDelay(5, Duration.ofSeconds(5))
        .filter(is5xxServerError)
        .onRetryExhaustedThrow((spec, signal) -> signal.failure());

Retry retry429 = Retry.fixedDelay(3, Duration.ofSeconds(20))
        .filter(is429)
        .onRetryExhaustedThrow((spec, signal) -> signal.failure());

// 组合多个重试规则,匹配到任意一个就执行对应重试
Retry combinedRetry = Retry.from(context -> 
    Flux.first(
        retry401.generateRetrySignals(context),
        retry5xx.generateRetrySignals(context),
        retry429.generateRetrySignals(context)
    )
);

同样将combinedRetry应用到WebClient的retryWhen即可。

关键说明

  • 避免连续调用retryWhen():每个retryWhen会替换上游的重试逻辑,所以最后一个会覆盖之前所有的配置。
  • 401场景的令牌刷新:必须在重试前完成,否则重试依然会失败,这里用doBeforeRetry来执行前置操作是最合适的时机。
  • 异常过滤:通过filter确保只有目标异常才会触发重试,避免无关异常被重试。

内容的提问来源于stack exchange,提问作者Naveen Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 20:27:36