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
相关产品推荐
相关产品推荐

