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

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) //---&gt; 为什么这里不生效?                 
                  .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()
}

问题排查与解决方案

核心原因分析

  1. 异常被包装且未被识别:BenefitService中block()会将TimeoutException包装成RuntimeException抛出,但断路器配置仅记录IOException和TimeoutException,包装后的异常不会被计入失败统计,导致断路器状态不更新。
  2. 重复断路器包裹: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 07:15:32