Resilience4j断路器使用Decorators时失效,WebClient场景正常
问题:Resilience4j Decorators搭配断路器不生效,WebClient搭配则正常
我正在尝试用编程方式实现Resilience4j断路器,通过REST端点强制切换断路器状态,但在ProfileService中使用Decorators时断路器无法正常工作;但在BenefitService中搭配WebClient使用CircuitBreakerOperator时却能正常运行。外部Benefit API设置为延迟10秒,调用会触发超时。
控制器类
@RestController @RequestMapping("v1/profileinfo") public class WebController { public static final Logger LOG = LoggerFactory.getLogger(WebController.class); @Autowired private CircuitBreakerRegistry circuitBreakerRegistry; @Autowired private AppConstants appConstants; @Autowired private ProfileService profileService; @GetMapping("/{profId}") public ResponseEntity<ProfielInfo> getProfileInfo(@PathVariable("profId") Long profId){ ProfielInfo resp = null; resp = profileService.getProfileInfo(profId); return new ResponseEntity<ProfielInfo>(resp, HttpStatus.OK); } @PutMapping("circuitbreaker/{state}") public String setCircuitBreaker(@PathVariable("state") String state) { if(state.toUpperCase().equals(appConstants.CB_CLOSED)) { circuitBreakerRegistry.circuitBreaker("benefitService").transitionToClosedState(); } if(state.toUpperCase().equals(appConstants.CB_OPEN)) { circuitBreakerRegistry.circuitBreaker("benefitService").transitionToForcedOpenState(); } return state; } }
ProfileService类
.... import java.util.concurrent.TimeoutException; import java.util.function.Supplier; import io.github.resilience4j.circuitbreaker.CallNotPermittedException; import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import io.github.resilience4j.decorators.Decorators; @Service public class ProfileService { public static final Logger LOG = LoggerFactory.getLogger(ProfileService.class); @Autowired private CircuitBreakerConfig circuitBreakerConfig; //ADDED @Autowired private CircuitBreakerRegistry circuitBreakerRegistry; //ADDED @Autowired private UserInfoService userInfoService; @Autowired private BenefitService benefitService; public ProfielInfo getProfileInfo(Long profId) throws TimeoutException { CircuitBreaker circuitBreaker = circuitBreakerRegistry .circuitBreaker("benefitService"); ProfielInfo profileInfo = null; profileInfo = new ProfielInfo(); UserInfo userInfo = null; userInfo = userInfoService.getUserInfo(profId); Benefit benefit = null; Supplier<Benefit> supplier = () -> benefitService.getBenefitInfo(profId); LOG.info("||||Calling benefitService..."); benefit = Decorators.ofSupplier(supplier) .withCircuitBreaker(circuitBreaker) //---> 为什么这里不生效? .withFallback(e -> buildFallbackBenefitInfo(profId)) .get(); profileInfo.setUserInfo(userInfo); profileInfo.setBenefit(benefit); LOG.info("---End of the ProfileService.getProfileInfo()---"); return profileInfo; } public Benefit buildFallbackBenefitInfo(Long memId) { Benefit benefit = null; benefit = new Benefit(); benefit.setBenefitId("00000"); benefit.setMemeberId("00000"); return benefit; } }
BenefitService类(WebClient搭配断路器正常工作)
@Service public class BenefitService { @Autowired private CircuitBreakerRegistry circuitBreakerRegistry; @Autowired private WebClient benefitApiClient; public Benefit getBenefitInfo(long profId) { CircuitBreaker circuitBreaker = circuitBreakerRegistry .circuitBreaker("benefitService"); return benefitApiClient.get() .uri("/" + profId) .retrieve() .bodyToMono(Benefit.class) .timeout(Duration.ofSeconds(3)) .transformDeferred(CircuitBreakerOperator.of(circuitBreaker)) // 这里正常工作 .block(); } }
断路器配置类
@Configuration public class CircuitBreakerConfigs { @Value("${circuitbreaker.failureRateThreshold}") private int failureRateThreshold; @Value("${circuitbreaker.slowCallRateThreshold}") private int slowCallRateThreshold; @Value("${circuitbreaker.waitDurationInOpenState}") private int waitDurationInOpenState; @Value("${circuitbreaker.slowCallDurationThreshold}") private int slowCallDurationThreshold; @Value("${circuitbreaker.permittedNumberOfCallsInHalfOpenState}") private int permittedNumHalfOpenState; @Value("${circuitbreaker.minimumNumberOfCalls}") private int minimumNumberOfCalls; @Value("${circuitbreaker.slidingWindowSize}") private int slidingWindowSize; @Bean public CircuitBreakerConfig circuitBreakerConfig() { return CircuitBreakerConfig.custom() .failureRateThreshold(failureRateThreshold) .slowCallRateThreshold(slowCallRateThreshold) .waitDurationInOpenState(Duration.ofSeconds(waitDurationInOpenState)) .slowCallDurationThreshold(Duration.ofSeconds(slowCallDurationThreshold)) .permittedNumberOfCallsInHalfOpenState(permittedNumHalfOpenState) .minimumNumberOfCalls(minimumNumberOfCalls) .slidingWindowType(SlidingWindowType.COUNT_BASED) .slidingWindowSize(slidingWindowSize) .enableAutomaticTransitionFromOpenToHalfOpen() .recordExceptions(IOException.class, TimeoutException.class) .build(); } @Bean public CircuitBreakerRegistry circuitBreakerRegistry() { return CircuitBreakerRegistry.of(circuitBreakerConfig()); } }
application.yml配置
server: port: 9099 management: endpoints: web: exposure: include: "*" resilience4j.circuitbreaker: configs: default: registerHealthIndicator: true circuitbreaker: failureRateThreshold: 50 slowCallRateThreshold: 50 waitDurationInOpenState: 60 slowCallDurationThreshold: 60 minimumNumberOfCalls: 1 permittedNumberOfCallsInHalfOpenState: 3 slidingWindowSize: 5
build.gradle配置
plugins { id 'org.springframework.boot' version '2.7.3' id 'io.spring.dependency-management' version '1.0.13.RELEASE' id 'java' } group = 'com.demo.ref' version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-webflux' developmentOnly 'org.springframework.boot:spring-boot-devtools' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.projectreactor:reactor-test' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-aop', version: '2.7.2' implementation group: 'io.github.resilience4j', name: 'resilience4j-spring-boot2', version: '1.5.0' implementation group: 'io.github.resilience4j', name: 'resilience4j-reactor', version: '1.5.0' implementation group: 'io.github.resilience4j', name: 'resilience4j-all', version: '1.5.0' implementation group: 'io.micrometer', name: 'micrometer-registry-prometheus', version: '1.9.3' } tasks.named('test') { useJUnitPlatform() }
问题排查与解决方案
核心原因分析
- 异常被包装且未被识别:BenefitService中
block()会将TimeoutException包装成RuntimeException抛出,但断路器配置仅记录IOException和TimeoutException,包装后的异常不会被计入失败统计,导致断路器状态不更新。 - 重复断路器包裹:ProfileService和BenefitService同时对同一个断路器实例进行包裹,BenefitService的断路器已经捕获了异常,ProfileService的Decorators无法感知到失败。
解决方案
1. 调整异常记录配置
在断路器配置中添加对包装异常的支持,确保所有失败场景能被识别:
@Bean public CircuitBreakerConfig circuitBreakerConfig() { return CircuitBreakerConfig.custom() // 保留原有配置 .recordExceptions(IOException.class, TimeoutException.class, RuntimeException.class) .build(); }
2. 移除重复的断路器包裹
选择在单一层级进行断路器包裹,推荐保留ProfileService的Decorators实现,移除BenefitService中的CircuitBreakerOperator:
@Service public class BenefitService { @Autowired private WebClient benefitApiClient; public Benefit getBenefitInfo(long profId) { return benefitApiClient.get() .uri("/" + profId) .retrieve() .bodyToMono(Benefit.class) .timeout(Duration.ofSeconds(3)) .block(); } }
3. 验证断路器状态
调用PUT /v1/profileinfo/circuitbreaker/OPEN强制打开断路器后,再次调用GET /v1/profileinfo/{profId},应直接返回fallback的Benefit数据,不会触发对外部API的调用。
内容的提问来源于stack exchange,提问作者Thomson Mathew
相关产品推荐
相关产品推荐

