Java진영 경량형 fault tolerance 라이브러리인 Resilience4j 기준으로 Circuit breacker에 대해 알아보자.
오류를 감지하여 외부 시스템 호출을 차단하거나 허용하는 역할로 시스템을 보호한다.
상태 | 설명 |
---|---|
CLOSED | 정상 상태. 모든 호출이 통과됨. 실패율을 모니터링함. |
OPEN | 실패율이 임계치를 넘으면 호출을 차단하고 바로 예외 반환. |
HALF_OPEN | 일정 시간 후 일부 요청만 허용. 성공률로 복구 여부 판단. |
상태 전이 조건 (기본 설정 기준):
sliding window
동안 실패율이 failureRateThreshold
이상.waitDurationInOpenState
시간 이후 전이됨.Circuit breaker는 실패율, 성공률, 호출 수 등을 슬라이딩 윈도우로 측정한다.
슬라이딩 윈도우는 count-based, time-based 두 가지로 나눌 수 있다.
Count-Based Sliding Window
마지막 N개의 호출 결과를 저장
기본값: 100
상태 전이는 이 N개의 호출 기준으로 결정됨
Time-Based Sliding Window
최근 T초 동안의 호출 기록을 기준으로 상태 판단
시간 기반에서는 호출이 적으면 상태 전이 늦어질 수 있음
실패로 간주할 예외 조건을 직접 정의할수 있고, 예외로 둘 수도 있다.
getState()
→ isCallPermitted()
)CallNotPermittedException
반환onSuccess
, onError
)CircuitBreakerRegistry
: 설정과 인스턴스를 관리CircuitBreakerStateMachine
: 상태 전이 및 통계 계산CircuitBreakerMetrics
: 호출 성공/실패 기록EventPublisher
: 상태 전이나 이벤트에 대한 publish이에 따라 이벤트 발생 시 Micrometer를 통한 Prometheus, CloudWatch 연동할 수 있다.
circuitBreaker.eventPublisher
.onStateTransition { event -> log.info("State changed: ${event.stateTransition}") }
기본 옵션은 실험적이거나 경량 서비스에 적절한 수준이다.
설정 항목 | 기본값 | (일반적인) 추천값 | 설명 |
---|---|---|---|
slidingWindowType | COUNT_BASED | COUNT_BASED | 슬라이딩 윈도우 타입 (카운트 기반) |
slidingWindowSize | 100 | 20~50 | 윈도우 내 호출 수 |
minimumNumberOfCalls | 100 | 10 | 실패율 계산을 위한 최소 호출 수 |
permittedNumberOfCallsInHalfOpenState | 10 | 3~5 | HALF_OPEN 상태에서 허용할 호출 수 |
waitDurationInOpenState | 60초 | 10~30초 | OPEN 상태 유지 시간 |
failureRateThreshold | 50% | 40~60 | 실패율 임계값 |
slowCallRateThreshold | 100% | 50% | 느린 호출 비율 임계값 |
slowCallDurationThreshold | 60초 | 2~5초 | "느린 호출"으로 간주할 최대 시간 |
recordExceptions | Throwable.class | 실패로 간주할 예외 유형 (기본: 모든 예외) | |
ignoreExceptions | 없음 | 무시할 예외 | |
automaticTransitionFromOpenToHalfOpenEnabled | false | true | 자동 상태 전이 허용 여부 |
writableStackTraceEnabled | true | 예외에 스택 트레이스 포함 여부 |
아래는 각 옵션에 대한 상세 설명과 내부 동작 방식이다.
옵션명 | 설명 | 내부 동작 방식 |
---|---|---|
slidingWindowType | 통계를 어떻게 수집할지 결정 (COUNT_BASED or TIME_BASED ) | COUNT_BASED : 고정 크기 순환 배열로 최근 N건 유지TIME_BASED : 시간 기반 큐에 통계 저장 |
slidingWindowSize | sliding window 크기 설정 (개수 또는 초 단위) | SlidingWindowMetrics 클래스에서 윈도우 버퍼를 생성새로운 호출 기록이 들어오면 오래된 것 제거 |
minimumNumberOfCalls | 상태 전이(OPEN) 판단을 위해 필요한 최소 호출 수 | 이 수치 미만이면 실패율 계산 자체를 하지 않음 |
failureRateThreshold | 실패율이 이 값을 넘으면 OPEN으로 전이됨 (%) | 호출 성공/실패를 기준으로 실패율 계산 예: 100건 중 51건 실패 → 51% → 전이 |
waitDurationInOpenState | OPEN 상태에서 HALF_OPEN으로 전환되기까지 대기 시간 | 상태가 OPEN이면 타이머 스케줄러 시작 (OpenState -> HalfOpenState ) |
permittedNumberOfCallsInHalfOpenState | HALF_OPEN 상태에서 허용할 호출 수 | 호출 성공/실패를 기준으로 HALF_OPEN에서 CLOSED/OPEN 결정 |
automaticTransitionFromOpenToHalfOpenEnabled | 타이머에 의해 자동으로 HALF_OPEN으로 전이할지 | true일 경우 내부 스케줄러로 타이머를 등록하여 시간 경과 후 상태 전이 |
slowCallRateThreshold | 느린 호출 비율이 이 값 넘으면 OPEN 전이 트리거 (%) | 호출 시간이 slowCallDurationThreshold 초과하면 느린 호출로 간주 |
slowCallDurationThreshold | 호출이 몇 초 이상 걸리면 느린 호출로 간주할지 (Duration) | StopWatch 로 호출 시간 측정 후 이 값과 비교 |
recordExceptions | 실패로 기록할 예외 유형들 | exception instanceof 로 체크해서 기록 여부 결정 |
ignoreExceptions | 실패율 계산에서 제외할 예외 유형들 | exception instanceof 로 체크 후 무시 |
writableStackTraceEnabled | 예외 발생 시 스택 트레이스를 포함할지 | 내부 예외 클래스에 stack trace 포함 여부 결정 (성능 최적화용) |
Resilient4j는 기본적으로 Thread-safe한 자바 객체 기반. In-memory로 상태를 유지한다.
그렇기 때문에 각 인스턴스별로만 상태를 유지할 수 있다(CircuitBreakerStateMachine
).
분산 서킷브레이커가 필요하다면, 솔루션을 쓰거나 상태 공유 방식으로 개발해야 할 수 있음.
외부 Fault Tolerance 솔루션
도구 | 특징 |
---|---|
Istio + Envoy | 서비스 메시 계층에서 Circuit Breaker 적용 분산 환경에서도 상태 공유 가능 |
Spring Cloud Gateway + RateLimiter | Spring Gateway에서 API 경로별 서킷브레이커 설정 |
Netflix Conductor / Sentinel (Alibaba) | 클러스터 레벨 흐름 제어 |
직접 상태(OPEN/CLOSED 등)를 Redis, Hazelcast, Zookeeper 등에 저장해 공유하는 방식
레디스 기준으로라면, 특정 키로 특정 API에 대한 서킷 상태를 공유할 수 있다.
아래와 같은 흐름으로 정리할 수 있음
그런데 이러면 따라오는 문제가 있다.
결국 상태 변경은 누군가 해줘야 하는데, OPEN 상태일 때 누가 상태를 변경하는 책임을 져야 할까?
이 문제를 해결하려면 스케줄링 처리해서 테스트 호출을 하거나,
아니면 분산락 등을 적용해서 회복 책임 인스턴스 하나만 선정할 수 있다.
좀 더 보완을 하자면 아예 TTL을 줘서 만료되어 키가 삭제되면 CLOSED로 간주할 수도 있겠다.