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

使用WebFlux WebClient的retryWhen时遭遇StackOverflowError问题

WebFlux WebClient重试功能引发StackOverflowError问题排查与修复

我在为WebFlux WebClient实现特定异常的内置重试功能,用来处理业务中端点不可用的场景。单个请求失败时代码运行正常,但在高负载压力测试下,代码陷入疑似无限循环并抛出StackOverflowError。

我的实现代码

private ExchangeFilterFunction retryFilterFunction(final Builder clientBuilder) {
  return (request, next) -> next.exchange(request)
    .retryWhen(globalRetryFunction(clientBuilder))
    .doOnError(throwable -> log.debug("Maximum retries exhausted for {}", clientBuilder.url));
}

Retry globalRetryFunction(final Builder clientBuilder) {
  return Retry.fixedDelay(clientBuilder.maxRetries, Duration.ofMillis(clientBuilder.delayBetweenRetries))
    .filter(throwable -> {
      final var exceptionCanonicalName = throwable.getClass().getCanonicalName();
      final var causeCanonicalName = throwable.getCause() != null
        ? throwable.getCause().getClass().getCanonicalName()
        : "";
      return webClientProperties.getExceptionsToRetry().contains(exceptionCanonicalName)
        || webClientProperties.getExceptionsToRetry().contains(causeCanonicalName)
        || clientBuilder.exceptionsToRetry.contains(exceptionCanonicalName)
        || clientBuilder.exceptionsToRetry.contains(causeCanonicalName);
    })
    .doBeforeRetry(retrySignal -> log.debug("Retrying for {} time", (retrySignal.totalRetries() + 1)));
}

问题现象

堆栈跟踪显示代码出现递归调用不断累积的情况,最终触发StackOverflowError。

我想解决的问题

  1. 是否可以为WebFlux WebClient创建带内置重试的客户端定义?
  2. 如何修复重试时不断递归调用自身、递归次数持续增加的问题?

问题原因

核心问题是重试逻辑被重复应用到了重试请求中:ExchangeFilterFunction会拦截所有通过WebClient的请求,包括重试时发起的新请求。每次重试都会再次触发retryWhen,形成递归调用链,最终导致栈溢出。

修复方案

1. 避免Filter递归应用重试逻辑

不要通过ExchangeFilterFunction添加重试,更推荐直接在具体请求链中指定重试,或者通过WebClient.Builder构建时确保重试逻辑仅生效一次。如果坚持用Filter,需给请求打标记避免重复处理:

private ExchangeFilterFunction retryFilterFunction(final Builder clientBuilder) {
  return (request, next) -> {
    // 检查请求是否已被重试处理过
    if (request.attributes().containsKey("RETRY_APPLIED")) {
      return next.exchange(request);
    }
    // 给请求添加标记,避免重试时重复应用逻辑
    ClientRequest markedRequest = ClientRequest.from(request)
        .attribute("RETRY_APPLIED", true)
        .build();
    return next.exchange(markedRequest)
        .retryWhen(globalRetryFunction(clientBuilder))
        .doOnError(throwable -> log.debug("Maximum retries exhausted for {}", clientBuilder.url));
  };
}

2. 优化重试逻辑的异常过滤

简化异常检查逻辑,同时确保重试次数严格生效:

Retry globalRetryFunction(final Builder clientBuilder) {
  // 合并全局和客户端的重试异常列表
  Set<String> retryExceptions = new HashSet<>(webClientProperties.getExceptionsToRetry());
  retryExceptions.addAll(clientBuilder.exceptionsToRetry);

  return Retry.fixedDelay(clientBuilder.maxRetries, Duration.ofMillis(clientBuilder.delayBetweenRetries))
    .filter(throwable -> {
      // 遍历异常链,检查是否有符合重试条件的异常
      Throwable current = throwable;
      while (current != null) {
        if (retryExceptions.contains(current.getClass().getCanonicalName())) {
          return true;
        }
        current = current.getCause();
      }
      return false;
    })
    .doBeforeRetry(retrySignal -> log.debug("Retrying for {} time", (retrySignal.totalRetries() + 1)))
    // 重试耗尽后直接抛出原异常,避免无限重试
    .onRetryExhaustedThrow((spec, signal) -> signal.failure());
}

3. 正确构建带内置重试的WebClient

可以创建带内置重试的WebClient实例,但需避免逻辑重复应用。更可靠的方式是在具体请求上添加重试:

// 全局配置重试规则
Retry retry = Retry.fixedDelay(3, Duration.ofMillis(500))
    .filter(throwable -> /* 异常过滤逻辑 */);

// 具体请求中使用重试
webClient.get()
    .uri("/api/endpoint")
    .retrieve()
    .bodyToMono(MyResponse.class)
    .retryWhen(retry);

如果要全局绑定重试,需确保Filter不会重复应用:

@Bean
public WebClient retryableWebClient(WebClient.Builder builder) {
  Retry retry = /* 重试规则 */;
  return builder
      .filter((request, next) -> {
        if (request.attributes().containsKey("RETRY_APPLIED")) {
          return next.exchange(request);
        }
        ClientRequest markedRequest = ClientRequest.from(request)
            .attribute("RETRY_APPLIED", true)
            .build();
        return next.exchange(markedRequest).retryWhen(retry);
      })
      .build();
}

结论

  • 可以为WebFlux WebClient创建带内置重试的客户端定义,但必须避免重试逻辑的递归应用。
  • 修复栈溢出的关键是确保重试逻辑仅对原始请求生效一次,通过请求属性标记或直接在请求链中添加重试即可实现。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 12:07:46