使用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。
我想解决的问题
- 是否可以为WebFlux WebClient创建带内置重试的客户端定义?
- 如何修复重试时不断递归调用自身、递归次数持续增加的问题?
问题原因
核心问题是重试逻辑被重复应用到了重试请求中: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
相关产品推荐
相关产品推荐

