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

ReactiveFeignClient与CircuitBreaker错误处理重复计数问题

解决方案

方案1:调整Fallback实现,返回正常响应而非抛出异常

问题核心之一是Fallback执行时抛出异常,导致CircuitBreaker的failurePredicate再次触发计数。修改FallbackFactory,返回正常的Mono/Flux响应,而非抛出异常:

@Component
public class SomeRestClientFallbackFactory implements FallbackFactory<SomeRestClient> {
    @Override
    public SomeRestClient create(Throwable cause) {
        return new SomeRestClient() {
            @Override
            public Mono<SomeResponse> getSomeData() {
                // 返回降级后的默认响应,避免抛出异常
                return Mono.just(SomeResponse.defaultFallback());
            }
        };
    }
}

同时,确保CircuitBreaker的failurePredicate仅针对原始Feign调用的异常,过滤掉Fallback相关的异常:

@Bean
public CircuitBreaker someCircuitBreaker() {
    return CircuitBreaker.of("someCircuitBreaker", CircuitBreakerConfig.custom()
            .failureRateThreshold(50)
            .waitDurationInOpenState(Duration.ofSeconds(10))
            .failurePredicate(throwable -> {
                // 仅处理Feign调用产生的业务异常(如404、5xx)
                return throwable instanceof FeignException feignException 
                        && (feignException.status() == 404 || feignException.status() >= 500);
            })
            .build());
}

方案2:用Resilience4j Retry替代自定义RetryableErrorDecoder

将重试逻辑从Reactive Feign的RetryableErrorDecoder迁移到Resilience4j的Retry组件,统一由Resilience4j管理重试与熔断的顺序,避免重复计数:

步骤1:配置Resilience4j Retry

@Configuration
public class Resilience4jConfig {
    @Bean
    public Retry someRetry() {
        return Retry.of("someRetry", RetryConfig.custom()
                .maxAttempts(3) // 重试次数
                .waitDuration(Duration.ofSeconds(1)) // 重试间隔
                // 根据响应结果判断是否重试
                .retryOnResult(response -> {
                    if (response instanceof ClientResponse clientResponse) {
                        HttpStatus status = clientResponse.statusCode();
                        return status.is4xxClientError() || status.is5xxServerError();
                    }
                    return false;
                })
                // 根据异常类型判断是否重试
                .retryOnException(throwable -> throwable instanceof FeignException || throwable instanceof IOException)
                .build());
    }

    @Bean
    public CircuitBreaker someCircuitBreaker() {
        return CircuitBreaker.of("someCircuitBreaker", CircuitBreakerConfig.custom()
                .failureRateThreshold(50)
                .waitDurationInOpenState(Duration.ofSeconds(10))
                .failurePredicate(throwable -> {
                    // 仅处理重试失败后的最终异常
                    return throwable instanceof FeignException feignException 
                            && (feignException.status() == 404 || feignException.status() >= 500);
                })
                .build());
    }
}

步骤2:修改Feign接口,移除自定义RetryableErrorDecoder,添加Resilience4j注解

@ReactiveFeignClient(name = "some-service", fallbackFactory = SomeRestClientFallbackFactory.class)
public interface SomeRestClient {
    @GetMapping("/some-endpoint")
    @Retry(name = "someRetry")
    @CircuitBreaker(name = "someCircuitBreaker")
    Mono<SomeResponse> getSomeData();
}

这种方式下,重试逻辑由Resilience4j Retry处理,重试失败后才会触发CircuitBreaker的熔断判断,Fallback仅在熔断或重试全部失败时执行,且返回正常响应,不会重复触发failurePredicate。

方案3:调整Reactive Feign与CircuitBreaker的执行顺序

如果坚持使用自定义RetryableErrorDecoder,需要确保CircuitBreaker仅在重试全部失败后才介入。可以通过将CircuitBreaker的代理放在Reactive Feign客户端的外层,而非直接注解在Feign方法上:

@Service
public class SomeService {
    private final SomeRestClient someRestClient;
    private final CircuitBreaker circuitBreaker;

    public SomeService(SomeRestClient someRestClient, CircuitBreakerRegistry circuitBreakerRegistry) {
        this.someRestClient = someRestClient;
        this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("someCircuitBreaker");
    }

    public Mono<SomeResponse> getSomeData() {
        // 先执行Feign的重试逻辑,再进入CircuitBreaker
        return circuitBreaker.executeMono(someRestClient::getSomeData)
                .onErrorResume(throwable -> {
                    // 这里处理熔断后的降级逻辑,替代FallbackFactory
                    return Mono.just(SomeResponse.defaultFallback());
                });
    }
}

同时,移除Feign接口上的@CircuitBreaker注解和fallbackFactory配置,避免重复触发。这种方式下,CircuitBreaker仅接收重试后的最终异常,不会因为Fallback而重复计数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 14:20:23