Circuit breaker

송재호·2025년 3월 6일
0

backend 지식 시리즈

목록 보기
1/1

Java진영 경량형 fault tolerance 라이브러리인 Resilience4j 기준으로 Circuit breacker에 대해 알아보자.

CircuitBreaker란?

오류를 감지하여 외부 시스템 호출을 차단하거나 허용하는 역할로 시스템을 보호한다.

CircuitBreaker의 상태 전이

상태설명
CLOSED정상 상태. 모든 호출이 통과됨. 실패율을 모니터링함.
OPEN실패율이 임계치를 넘으면 호출을 차단하고 바로 예외 반환.
HALF_OPEN일정 시간 후 일부 요청만 허용. 성공률로 복구 여부 판단.

상태 전이 조건 (기본 설정 기준):

  • CLOSED → OPEN
    sliding window 동안 실패율이 failureRateThreshold 이상.
  • OPEN → HALF_OPEN
    waitDurationInOpenState 시간 이후 전이됨.
  • HALF_OPEN → CLOSED
    제한된 호출 중 성공률이 충분히 높으면 복구로 판단.
  • HALF_OPEN → OPEN
    허용된 호출 중 일부라도 실패하면 다시 OPEN.

Sliding Window와 통계 방식

Circuit breaker는 실패율, 성공률, 호출 수 등을 슬라이딩 윈도우로 측정한다.
슬라이딩 윈도우는 count-based, time-based 두 가지로 나눌 수 있다.

Count-Based Sliding Window
마지막 N개의 호출 결과를 저장
기본값: 100
상태 전이는 이 N개의 호출 기준으로 결정됨

Time-Based Sliding Window
최근 T초 동안의 호출 기록을 기준으로 상태 판단
시간 기반에서는 호출이 적으면 상태 전이 늦어질 수 있음

실패 판단 기준

실패로 간주할 예외 조건을 직접 정의할수 있고, 예외로 둘 수도 있다.

  • RecordFailurePredicate: 실패로 간주할 예외 조건을 직접 정의 가능
  • ignoreExceptions: 실패율 계산에서 제외할 예외

호출 흐름

  1. 호출 전 상태 체크 (getState()isCallPermitted())
  2. 호출이 차단되면 즉시 CallNotPermittedException 반환
  3. 호출이 실행되면 결과 기록 (onSuccess, onError)
  4. Sliding Window에 기록 추가
  5. 실패율 계산 → 상태 전이 여부 판단

내부 구조

  • CircuitBreakerRegistry: 설정과 인스턴스를 관리
  • CircuitBreakerStateMachine: 상태 전이 및 통계 계산
  • CircuitBreakerMetrics: 호출 성공/실패 기록
  • EventPublisher: 상태 전이나 이벤트에 대한 publish

이에 따라 이벤트 발생 시 Micrometer를 통한 Prometheus, CloudWatch 연동할 수 있다.

circuitBreaker.eventPublisher
    .onStateTransition { event -> log.info("State changed: ${event.stateTransition}") }

기본 옵션 & 옵션 상세

기본 옵션은 실험적이거나 경량 서비스에 적절한 수준이다.

설정 항목기본값(일반적인) 추천값설명
slidingWindowTypeCOUNT_BASEDCOUNT_BASED슬라이딩 윈도우 타입 (카운트 기반)
slidingWindowSize10020~50윈도우 내 호출 수
minimumNumberOfCalls10010실패율 계산을 위한 최소 호출 수
permittedNumberOfCallsInHalfOpenState103~5HALF_OPEN 상태에서 허용할 호출 수
waitDurationInOpenState60초10~30초OPEN 상태 유지 시간
failureRateThreshold50%40~60실패율 임계값
slowCallRateThreshold100%50%느린 호출 비율 임계값
slowCallDurationThreshold60초2~5초"느린 호출"으로 간주할 최대 시간
recordExceptionsThrowable.class실패로 간주할 예외 유형 (기본: 모든 예외)
ignoreExceptions없음무시할 예외
automaticTransitionFromOpenToHalfOpenEnabledfalsetrue자동 상태 전이 허용 여부
writableStackTraceEnabledtrue예외에 스택 트레이스 포함 여부

아래는 각 옵션에 대한 상세 설명과 내부 동작 방식이다.

옵션명설명내부 동작 방식
slidingWindowType통계를 어떻게 수집할지 결정 (COUNT_BASED or TIME_BASED)COUNT_BASED: 고정 크기 순환 배열로 최근 N건 유지
TIME_BASED: 시간 기반 큐에 통계 저장
slidingWindowSizesliding window 크기 설정
(개수 또는 초 단위)
SlidingWindowMetrics 클래스에서 윈도우 버퍼를 생성
새로운 호출 기록이 들어오면 오래된 것 제거
minimumNumberOfCalls상태 전이(OPEN) 판단을 위해 필요한 최소 호출 수이 수치 미만이면 실패율 계산 자체를 하지 않음
failureRateThreshold실패율이 이 값을 넘으면 OPEN으로 전이됨 (%)호출 성공/실패를 기준으로 실패율 계산
예: 100건 중 51건 실패 → 51% → 전이
waitDurationInOpenStateOPEN 상태에서 HALF_OPEN으로 전환되기까지 대기 시간상태가 OPEN이면 타이머 스케줄러 시작 (OpenState -> HalfOpenState)
permittedNumberOfCallsInHalfOpenStateHALF_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 + RateLimiterSpring Gateway에서 API 경로별 서킷브레이커 설정
Netflix Conductor / Sentinel (Alibaba)클러스터 레벨 흐름 제어

직접 상태(OPEN/CLOSED 등)를 Redis, Hazelcast, Zookeeper 등에 저장해 공유하는 방식

레디스 기준으로라면, 특정 키로 특정 API에 대한 서킷 상태를 공유할 수 있다.
아래와 같은 흐름으로 정리할 수 있음

  1. 서킷 상태를 Redis로부터 읽기
  2. OPEN 상태면 즉시 fallback 또는 예외 처리
  3. 호출 시도 후 실패율/슬로우콜 비율 계산하여 상태 변경 판단
  4. 상태가 변경되면 Redis에 쓰기

그런데 이러면 따라오는 문제가 있다.
결국 상태 변경은 누군가 해줘야 하는데, OPEN 상태일 때 누가 상태를 변경하는 책임을 져야 할까?

이 문제를 해결하려면 스케줄링 처리해서 테스트 호출을 하거나,
아니면 분산락 등을 적용해서 회복 책임 인스턴스 하나만 선정할 수 있다.

좀 더 보완을 하자면 아예 TTL을 줘서 만료되어 키가 삭제되면 CLOSED로 간주할 수도 있겠다.

profile
식지 않는 감자

0개의 댓글