서킷 브레이커

외부 서비스 호출 실패가 반복되면 일정 시간 동안 호출을 차단하는 패턴입니다. 전기 회로의 차단기(Circuit Breaker)에서 이름을 따왔습니다.

비유

[전기 차단기]
과전류 발생 → 차단기 내림 → 화재 방지

[서킷브레이커]
서비스 장애 → 호출 차단 → 시스템 보호

왜 필요한가?

[서킷브레이커 없이]
결제 서비스 장애 발생
    ↓
계속 호출 시도 (타임아웃 대기)
    ↓
스레드/커넥션 고갈
    ↓
전체 시스템 마비 (장애 전파)
[서킷브레이커 적용]
결제 서비스 장애 발생
    ↓
실패 감지 → 서킷 열림
    ↓
즉시 실패 반환 (빠른 실패)
    ↓
다른 서비스 정상 운영

세 가지 상태

        실패율 초과
    ┌───────────────┐
    ▼               │
 [CLOSED] ────▶ [OPEN]
    ▲               │
    │               │ 대기 시간 경과
    │               ▼
    └─────── [HALF_OPEN]
        성공        실패
        ↓           ↓
      CLOSED      OPEN
상태동작
CLOSED정상 호출, 실패 모니터링
OPEN호출 차단, 즉시 실패 반환
HALF_OPEN일부 요청 허용, 상태 판단

Resilience4j로 구현

기본 설정

CircuitBreakerConfig config = CircuitBreakerConfig.custom()
    .failureRateThreshold(50)              // 실패율 50% 이상이면 OPEN
    .slowCallRateThreshold(80)             // 느린 호출 80% 이상이면 OPEN
    .slowCallDurationThreshold(Duration.ofSeconds(2))  // 2초 이상이면 느린 호출
    .waitDurationInOpenState(Duration.ofSeconds(30))   // OPEN 후 30초 대기
    .permittedNumberOfCallsInHalfOpenState(5)          // HALF_OPEN에서 5개 시도
    .slidingWindowSize(10)                 // 최근 10개 요청 기준
    .build();

CircuitBreaker circuitBreaker = CircuitBreaker.of("paymentService", config);

Spring Boot 설정

resilience4j:
  circuitbreaker:
    instances:
      paymentService:
        failure-rate-threshold: 50
        slow-call-rate-threshold: 80
        slow-call-duration-threshold: 2s
        wait-duration-in-open-state: 30s
        permitted-number-of-calls-in-half-open-state: 5
        sliding-window-size: 10

어노테이션 적용

@Service
public class PaymentService {

    @CircuitBreaker(name = "paymentService", fallbackMethod = "fallback")
    public Payment process(PaymentRequest request) {
        return paymentGateway.charge(request);
    }

    // 서킷 열렸을 때 대체 로직
    private Payment fallback(PaymentRequest request, Exception e) {
        return Payment.pending("결제 서비스 일시 중단");
    }
}

Fallback 패턴

@CircuitBreaker(name = "product", fallbackMethod = "getProductFallback")
public Product getProduct(Long id) {
    return productService.findById(id);  // 외부 서비스 호출
}

// 대체 응답
private Product getProductFallback(Long id, Exception e) {
    return cachedProducts.get(id);  // 캐시에서 반환
}

서킷브레이커 vs 벌크헤드 vs 재시도

패턴목적동작
서킷브레이커장애 전파 방지실패 시 호출 차단
벌크헤드리소스 격리동시 요청 수 제한
재시도일시적 오류 복구실패 시 재시도
// 함께 사용
@Bulkhead(name = "payment")
@CircuitBreaker(name = "payment")
@Retry(name = "payment")
public Payment process(PaymentRequest request) {
    return paymentGateway.charge(request);
}

실무 적용

[주문 서비스]
    │
    ├─▶ 결제 서비스 ─── [서킷브레이커] ─── 결제 실패 시 "결제 대기" 상태로
    │
    ├─▶ 재고 서비스 ─── [서킷브레이커] ─── 재고 확인 실패 시 캐시 사용
    │
    └─▶ 알림 서비스 ─── [서킷브레이커] ─── 알림 실패 시 큐에 저장

CSLOG에서 보기