서킷브레이커

 서킷 브레이커(Circuit Breaker)와 Resilience4j 

서킷 브레이커(Circuit Breaker) 패턴과 이를 스프링 환경에서 지원하는 Resilience4j 알아보기


1. 서킷 브레이커란? 

집에서 전기를 너무 많이 쓰면 과부하를 막기 위해 '두꺼비집(누전 차단기)'이 내려가는 것을 본 적 있으신가요? 소프트웨어의 서킷 브레이커도 똑같은 역할을 합니다. 외부 서비스 호출 실패를 감지하여 빠른 실패(Fast Fail)를 유도하고, 장애를 격리하여 내 시스템의 전체적인 안정성을 유지합니다.

서킷 브레이커는 크게 3가지 상태(State)를 가집니다.

  • 🟢 Closed (정상): 두꺼비집이 올라가 있는 상태입니다. 모든 요청이 정상적으로 통과하며, 실패 시 카운터가 올라갑니다.
  • 🔴 Open (차단): 실패율이 임계값을 넘으면 두꺼비집이 내려갑니다. 이때부터는 외부로 요청을 보내지 않고 즉시 에러를 반환하여 대기 시간을 없앱니다.
  • 🟡 Half-Open (반개방): 일정 시간이 지나면 두꺼비집을 살짝 올려봅니다. 제한된 수의 요청만 보내보고, 성공하면 다시 Closed로, 실패하면 다시 Open으로 돌아갑니다.

2. Resilience4j 적용 및 필수 설정 (Spring Boot 3.x 기준)

스프링 부트 환경에서 서킷 브레이커를 적용하기 위해 Resilience4j를 사용합니다. 주의할 점은 Spring Boot 3.x 버전을 사용할 경우 의존성 추가 시 resilience4j-spring-boot3를 명시해야 한다는 것입니다.

1) 의존성 추가 (build.gradle)


dependencies {
    // Spring Boot 3.x용 Resilience4j
    implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
    implementation 'org.springframework.boot:spring-boot-starter-aop'
}
    

2) 핵심 설정 (application.yml)

동작 방식을 제어하는 필수 설정값들을 살펴보겠습니다.


resilience4j:
  circuitbreaker:
    configs:
      default:
        slidingWindowType: COUNT_BASED # 호출 횟수 기반으로 상태 결정
        slidingWindowSize: 5 # 최근 5번의 호출 기록을 확인
        minimumNumberOfCalls: 5 # 최소 5번은 호출되어야 실패율 계산 시작
        failureRateThreshold: 50 # 실패율이 50%를 넘으면 Open 상태로 전환
        waitDurationInOpenState: 20s # Open 상태 유지 시간 (20초 후 Half-Open)
        permittedNumberOfCallsInHalfOpenState: 3 # Half-Open 상태에서 허용할 테스트 호출 수
    

3. 코드 한눈에 보기: Fallback과 이벤트 리스너

실제 상품(Product) 정보를 조회하는 서비스 코드에 서킷 브레이커를 달아보겠습니다.


@Service
@RequiredArgsConstructor
public class ProductService {
    
    private final Logger log = LoggerFactory.getLogger(getClass());
    private final CircuitBreakerRegistry circuitBreakerRegistry;

    // 상태 변화를 모니터링하기 위한 이벤트 리스너 등록
    @PostConstruct
    public void registerEventListener() {
        circuitBreakerRegistry.circuitBreaker("productService").getEventPublisher()
            .onStateTransition(event -> log.info("### 상태 변경: {}", event))
            .onError(event -> log.info("### 에러 발생: {}", event));
    }

    // 서킷 브레이커 적용 및 실패 시 동작할 fallback 메서드 지정
    @CircuitBreaker(name = "productService", fallbackMethod = "fallbackGetProductDetails")
    public Product getProductDetails(String productId) {
        if ("111".equals(productId)) {
            throw new RuntimeException("강제 에러 발생!"); // 강제 실패 테스트
        }
        return new Product(productId, "정상 상품");
    }

    // Fallback 로직: 장애 발생 시 대체 응답
    public Product fallbackGetProductDetails(String productId, Throwable t) {
        log.error("### Fallback 실행: {}", t.getMessage());
        return new Product(productId, "대체 상품(기본값)");
    }
}
    

위 코드에서 가장 중요한 것은 Fallback 메커니즘입니다. 대상 서버가 죽어서 예외가 발생하더라도, 사용자는 시스템 에러 화면 대신 fallbackGetProductDetails가 반환하는 "대체 상품(기본값)"이라는 안전한 응답을 받게 된다.


💡 개발자가 반드시 알아야 할 포인트!

1. Hystrix는 이제 그만! Resilience4j가 대세
과거 넷플릭스에서 만든 Hystrix가 널리 쓰였지만, 현재는 유지보수가 중단(Deprecated)되었습니다.
Java 8 이상의 함수형 프로그래밍에 맞게 설계되고 가벼운 Resilience4j가 현재 Spring 생태계의 완벽한 표준이다.

2. 모니터링 없는 서킷 브레이커는 눈감고 운전하는 것과 같습니다.
서킷이 Open 되었는지 여부를 개발자가 즉각적으로 알아야 합니다.
실무에서는 위 예제에 포함된 spring-boot-starter-actuatormicrometer-registry-prometheus를 활용하여 /actuator/prometheus 엔드포인트를 열고, Grafana 대시보드와 연동하여 실시간으로 상태를 시각화합니다.
슬랙(Slack) 알림을 연동하는 것도 필수입니다.

3. Fallback 디자인은 UX와 직결됩니다.
서버가 죽었다고 해서 무조건 빈 객체나 에러 메시지를 던져서는 안 된다.. 예를 들어 '추천 상품 목록'을 가져오는 API가 죽었다면, 에러를 내뿜는 대신 서버 내부 메모리에 저장된 '가장 많이 팔린 상품(Best Seller)' 목록을 Fallback으로 내려주어
사용자는 장애를 전혀 눈치채지 못하게 설계하는 것이 진정한 시니어 백엔드 개발자의 노하우라고 한다. 


 

'MSA' 카테고리의 다른 글

OpenFeign 공식 문서 ,선언적 HTTP 클라이언트의 모든 것  (0) 2026.05.15
API 게이트웨이  (0) 2026.04.15
로드밸런싱  (0) 2026.04.14
서비스 디스커버리  (0) 2026.04.13
Spring Cloud  (0) 2026.04.13