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

WebClient的retryWhen与onErrorResume是否互斥?重试未生效问题

问题说明

实现WebClient特定异常重试逻辑时,抛出PrematureCloseException后配置的重试逻辑未触发,需要确认retryWhen与onErrorResume是否互斥,以及重试失效的根因。
原有实现代码:

return client
        .sendWebhook(request, url)
        .exchangeToMono(
            response -> {
              final HttpStatus status = response.statusCode();
              return response
                  .bodyToMono(String.class)
                  .defaultIfEmpty(StringUtils.EMPTY)
                  .map(
                      body -> {
                        if (status.is2xxSuccessful()) {
                          log.info("HTTP_SUCCESS[{}][{}] body[{}]", functionName, company, body);
                          return ResponseEntity.ok().body(body);
                        } else {
                          log.warn(
                              format(
                                  "HTTP_ERROR[%s][%s] status[%s] body[%s]",
                                  functionName, company, status, body));
                          return status.is4xxClientError()
                              ? ResponseEntity.badRequest().body(body)
                              : ResponseEntity.internalServerError().body(body);
                        }
                      });
            })
        .retryWhen(
            Retry.backoff(1, Duration.ofSeconds(1))
                .filter(
                    err -> {
                      if (err instanceof PrematureCloseException) {
                        log.warn("PrematureCloseException detected retrying.");
                        return true;
                      }
                      return false;
                    }))
        .onErrorResume(
            ex -> {
              log.warn(
                  format(
                      "HTTP_ERROR[%s][%s] errorInternal[%s]",
                      functionName, company, ex.getMessage()));
              return Mono.just(ResponseEntity.internalServerError().body(ex.getMessage()));
            });
核心结论
  • retryWhen和onErrorResume不存在互斥关系,二者的执行逻辑完全由在响应式流中的位置决定:错误信号从上游向下游传播,先接触到错误的操作符先处理。
  • 你当前代码重试失效的核心原因是异常匹配逻辑存在缺陷,retryWhen的过滤器没有识别到被上层包装的PrematureCloseException,因此没有触发重试。
原因详解
  1. 操作符执行逻辑
    响应式流的信号传播规则是:订阅从下游向上游触发,错误/完成信号从上游向下游传递。你当前代码的操作符顺序本身是正确的:顺序为「请求发送+响应处理逻辑 -> retryWhen -> onErrorResume」,执行逻辑为:
    • 上游抛出错误后,会先传递给retryWhen,符合重试条件则重新订阅上游逻辑发起重试
    • 重试次数耗尽仍抛出错误时,错误才会传递给onErrorResume,走兜底返回逻辑
      如果顺序写反(onErrorResume放在retryWhen更靠近上游的位置),错误会先被onErrorResume捕获并返回正常的兜底响应,retryWhen接收不到错误信号,才会出现重试完全不触发的问题。
  2. 异常匹配缺陷
    Netty层抛出的PrematureCloseException在经过Spring WebClient、Reactor核心的传播过程中,大概率会被包装在WebClientRequestException、Exceptions.ReactiveException等外层异常中,你当前的过滤器仅判断顶层异常是否为PrematureCloseException,无法匹配到被包装的目标异常,因此过滤器始终返回false,不会触发重试,这也是你没有看到过滤器中打印的"PrematureCloseException detected retrying."日志的直接原因。
修复方案

调整retryWhen的异常过滤逻辑,递归遍历异常链匹配目标异常,修改后的过滤器代码如下:

.filter(
    err -> {
        Throwable current = err;
        // 递归遍历整个异常cause链
        while (current != null) {
            if (current instanceof PrematureCloseException) {
                log.warn("PrematureCloseException detected retrying.");
                return true;
            }
            current = current.getCause();
        }
        return false;
    })

额外注意事项:

  • 如果重试的请求带有请求体,需要确保请求体是可重放的(比如字节数组、字符串等已经缓存的内容,而非一次性的数据流),否则重试时会出现请求体已消费的错误。
  • 调试阶段可以在过滤器中打印异常的完整类型和cause链,确认实际抛出的异常结构,避免匹配遗漏。

内容的提问来源于stack exchange,提问作者Jonathan Chevalier

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 11:30:43