Spring Boot中Resilience4j多微服务调用的局部熔断降级实现问题
Spring Boot微服务熔断问题:单服务故障时独立返回默认响应
问题场景
用Spring Boot构建微服务,基于Resilience4j实现熔断机制,需求是:当调用的两个微服务中一个故障、一个正常时,故障服务返回默认响应,正常服务返回真实有效数据。
当前遇到的问题:
- 关闭Department Service模拟故障时,请求直接返回内部服务器错误,错误信息为
DEPARTMENT-SERVICE executing GET <<ENDPOINT_OF_DEPARTMENT_SERVICE>> - 若将
@CircuitBreaker注解加到getEmployeeById方法上,虽然不会报错,但只要一个服务故障,两个服务都会返回默认响应,不符合需求 - 已使用API网关做请求集中路由
当前代码实现
EmployeeServiceImpl.java
public class EmployeeServiceImpl implements EmployeeService { private EmployeeRepository employeeRepository; private DepartmentApiClient departmentApiClient; private OrganizationApiClient organizationApiClient; @Override public ApiResponseDto getEmployeeById(Long id) { Employee employee = employeeRepository.findById(id).orElseThrow( () -> new ResourceNotFoundException("Employee", "id", id) ); DepartmentDto departmentDto = getDepartmentInfo(employee.getDepartmentCode()); OrganizationDto organizationDto = getOrganizationInfo(employee.getOrganizationCode()); ApiResponseDto apiResponseDto = new ApiResponseDto(); apiResponseDto.setDepartmentDto(departmentDto); apiResponseDto.setEmployeeDto(AutoEmployeeMapper.MAPPER.mapToEmployeeDto(employee)); apiResponseDto.setOrganizationDto(organizationDto); return apiResponseDto; } @CircuitBreaker(name = "DEPARTMENT-SERVICE", fallbackMethod = "getDefaultDepartment") public DepartmentDto getDepartmentInfo(String departmentCode) { return departmentApiClient.getDepartmentByCode(departmentCode); } @CircuitBreaker(name = "ORGANIZATION-SERVICE", fallbackMethod = "getDefaultOrganization") public OrganizationDto getOrganizationInfo(String organizationCode) { return organizationApiClient.getOrganizationByCode(organizationCode); } public DepartmentDto getDefaultDepartment(Exception ex) { DepartmentDto defaultDepartment = new DepartmentDto(); defaultDepartment.setDepartmentCode("DEFAULT"); defaultDepartment.setDepartmentName("DEFAULT DEPARTMENT"); defaultDepartment.setDepartmentDescription("Default Department"); return defaultDepartment; } public OrganizationDto getDefaultOrganization(Exception ex) { OrganizationDto defaultOrganization = new OrganizationDto(); defaultOrganization.setOrganizationCode("DEFAULT"); defaultOrganization.setOrganizationName("DEFAULT ORG"); defaultOrganization.setOrganizationDescription("Default Organization"); return defaultOrganization; } }
初始application.properties配置
resilience4j.circuitbreaker.instances.EMPLOYEE-SERVICE.registerHealthIndicator=true resilience4j.circuitbreaker.instances.EMPLOYEE-SERVICE.failureRateThreshold=50 resilience4j.circuitbreaker.instances.EMPLOYEE-SERVICE.minimumNumberOfCalls=5 resilience4j.circuitbreaker.instances.EMPLOYEE-SERVICE.automaticTransitionFromOpenToHalfOpenEnabled=true resilience4j.circuitbreaker.instances.EMPLOYEE-SERVICE.waitDurationInOpenState=5s resilience4j.circuitbreaker.instances.EMPLOYEE-SERVICE.permittedNumberOfCallsInHalfOpenState=3 resilience4j.circuitbreaker.instances.EMPLOYEE-SERVICE.slidingWindowType=COUNT_BASED resilience4j.circuitbreaker.instances.EMPLOYEE-SERVICE.slidingWindowSize=10
新增的服务熔断配置(未解决问题)
resilience4j.circuitbreaker.instances.DEPARTMENT-SERVICE.registerHealthIndicator=true resilience4j.circuitbreaker.instances.DEPARTMENT-SERVICE.failureRateThreshold=50 resilience4j.circuitbreaker.instances.DEPARTMENT-SERVICE.minimumNumberOfCalls=5 resilience4j.circuitbreaker.instances.DEPARTMENT-SERVICE.automaticTransitionFromOpenToHalfOpenEnabled=true resilience4j.circuitbreaker.instances.DEPARTMENT-SERVICE.waitDurationInOpenState=5s resilience4j.circuitbreaker.instances.DEPARTMENT-SERVICE.permittedNumberOfCallsInHalfOpenState=3 resilience4j.circuitbreaker.instances.DEPARTMENT-SERVICE.slidingWindowType=COUNT_BASED resilience4j.circuitbreaker.instances.DEPARTMENT-SERVICE.slidingWindowSize=10 resilience4j.circuitbreaker.instances.ORGANIZATION-SERVICE.registerHealthIndicator=true resilience4j.circuitbreaker.instances.ORGANIZATION-SERVICE.failureRateThreshold=50 resilience4j.circuitbreaker.instances.ORGANIZATION-SERVICE.minimumNumberOfCalls=5 resilience4j.circuitbreaker.instances.ORGANIZATION-SERVICE.automaticTransitionFromOpenToHalfOpenEnabled=true resilience4j.circuitbreaker.instances.ORGANIZATION-SERVICE.waitDurationInOpenState=5s resilience4j.circuitbreaker.instances.ORGANIZATION-SERVICE.permittedNumberOfCallsInHalfOpenState=3 resilience4j.circuitbreaker.instances.ORGANIZATION-SERVICE.slidingWindowType=COUNT_BASED resilience4j.circuitbreaker.instances.ORGANIZATION-SERVICE.slidingWindowSize=10
问题原因与解决方案
核心原因:内部调用导致AOP代理失效
Spring AOP基于动态代理实现,同一个类内部调用带有@CircuitBreaker注解的方法时,不会经过代理对象,导致熔断逻辑完全不生效,调用故障服务时直接抛出异常,触发500错误。
解决步骤
- 拆分Feign调用到独立Bean
将带熔断注解的方法抽离到单独的Spring Bean中,确保调用时经过代理,熔断逻辑生效:
@Component public class ServiceClient { private final DepartmentApiClient departmentApiClient; private final OrganizationApiClient organizationApiClient; public ServiceClient(DepartmentApiClient departmentApiClient, OrganizationApiClient organizationApiClient) { this.departmentApiClient = departmentApiClient; this.organizationApiClient = organizationApiClient; } @CircuitBreaker(name = "DEPARTMENT-SERVICE", fallbackMethod = "getDefaultDepartment") public DepartmentDto getDepartmentInfo(String departmentCode) { return departmentApiClient.getDepartmentByCode(departmentCode); } @CircuitBreaker(name = "ORGANIZATION-SERVICE", fallbackMethod = "getDefaultOrganization") public OrganizationDto getOrganizationInfo(String organizationCode) { return organizationApiClient.getOrganizationByCode(organizationCode); } // Fallback方法需匹配原方法参数,再加Exception参数 public DepartmentDto getDefaultDepartment(String departmentCode, Exception ex) { DepartmentDto defaultDepartment = new DepartmentDto(); defaultDepartment.setDepartmentCode("DEFAULT"); defaultDepartment.setDepartmentName("DEFAULT DEPARTMENT"); defaultDepartment.setDepartmentDescription("Default Department"); return defaultDepartment; } public OrganizationDto getDefaultOrganization(String organizationCode, Exception ex) { OrganizationDto defaultOrganization = new OrganizationDto(); defaultOrganization.setOrganizationCode("DEFAULT"); defaultOrganization.setOrganizationName("DEFAULT ORG"); defaultOrganization.setOrganizationDescription("Default Organization"); return defaultOrganization; } }
- 修改EmployeeServiceImpl,注入ServiceClient
public class EmployeeServiceImpl implements EmployeeService { private final EmployeeRepository employeeRepository; private final ServiceClient serviceClient; public EmployeeServiceImpl(EmployeeRepository employeeRepository, ServiceClient serviceClient) { this.employeeRepository = employeeRepository; this.serviceClient = serviceClient; } @Override public ApiResponseDto getEmployeeById(Long id) { Employee employee = employeeRepository.findById(id).orElseThrow( () -> new ResourceNotFoundException("Employee", "id", id) ); DepartmentDto departmentDto = serviceClient.getDepartmentInfo(employee.getDepartmentCode()); OrganizationDto organizationDto = serviceClient.getOrganizationInfo(employee.getOrganizationCode()); ApiResponseDto apiResponseDto = new ApiResponseDto(); apiResponseDto.setDepartmentDto(departmentDto); apiResponseDto.setEmployeeDto(AutoEmployeeMapper.MAPPER.mapToEmployeeDto(employee)); apiResponseDto.setOrganizationDto(organizationDto); return apiResponseDto; } }
- 保留服务熔断配置
你新增的DEPARTMENT-SERVICE和ORGANIZATION-SERVICE熔断配置是正确的,需保留,确保两个服务的熔断实例独立工作。
验证效果
关闭Department Service后,调用getEmployeeById接口:
getDepartmentInfo触发熔断,返回默认DepartmentDtogetOrganizationInfo正常调用健康的Organization Service,返回真实数据- 最终响应中两个字段分别为默认值和真实值,符合需求
内容的提问来源于stack exchange,提问作者Monish
相关产品推荐
相关产品推荐

