서킷 브레이커
외부 서비스 호출 실패가 반복되면 일정 시간 동안 호출을 차단하는 패턴입니다. 전기 회로의 차단기(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);
}
실무 적용
[주문 서비스]
│
├─▶ 결제 서비스 ─── [서킷브레이커] ─── 결제 실패 시 "결제 대기" 상태로
│
├─▶ 재고 서비스 ─── [서킷브레이커] ─── 재고 확인 실패 시 캐시 사용
│
└─▶ 알림 서비스 ─── [서킷브레이커] ─── 알림 실패 시 큐에 저장