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

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错误。

解决步骤

  1. 拆分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;
    }
}
  1. 修改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;
    }
}
  1. 保留服务熔断配置
    你新增的DEPARTMENT-SERVICE和ORGANIZATION-SERVICE熔断配置是正确的,需保留,确保两个服务的熔断实例独立工作。

验证效果

关闭Department Service后,调用getEmployeeById接口:

  • getDepartmentInfo触发熔断,返回默认DepartmentDto
  • getOrganizationInfo正常调用健康的Organization Service,返回真实数据
  • 最终响应中两个字段分别为默认值和真实值,符合需求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 08:30:54