📦
signature-hyeon's tech blog
  • Intro
  • TECH
    • DATABASE
      • MongoDB
    • DEVOPS
      • 배포 전략 (Rolling, Blue/Green, Canary)
    • BACKEND
      • “왜 이렇게 느려?”를 마주했을 때
      • Resilience4j CircuitBreaker
  • STUDY
    • ELASTICSEARCH
      • Elasticsearch 란?
      • 색인과 검색 원리
    • 클린 코드
      • 3장 함수
    • 친절한 SQL 튜닝
      • 1장 SQL 처리 과정과 I/O
      • 2장 인덱스 기본
      • 3.3장 인덱스 스캔 효율화
      • 4.2장 소트 머지 조인
      • 4.3장 해시 조인
      • 5장 소트 튜닝
      • 5.4장 Sort Area 효율적으로 쓰는 SQL 작성법
      • 7장 SQL 옵티마이저
Powered by GitBook
On this page
  1. TECH
  2. BACKEND

Resilience4j CircuitBreaker

Resilience4j CircuitBreaker에 대해

Previous“왜 이렇게 느려?”를 마주했을 때NextELASTICSEARCH

Last updated 23 days ago

CtrlK
  • 1. 왜 맨날 나만 터지는 건데
  • 2. 세상엔 3가지 상태가 있다
  • 3. 세팅 잘못하면 진짜 터짐
  • 4. 한 줄이면 끝남
  • 5. 망했을 때 플랜 B가 있어야지
  • 6. 죽었는지 살았는지 어떻게 알아?

1. 왜 맨날 나만 터지는 건데

서킷 브레이커가 필요한 이유

이럴 때 있잖아요

  • 외부 API가 응답 안 하는데

  • 우리 서비스는 계속 때림

  • 결국 우리 서비스도 터짐

🧯 그래서 Circuit Breaker가 필요하다

서킷 브레이커는 실패가 일정 기준을 넘으면 해당 요청을 일시적으로 끊는다. 바로 재시도하지 않고 잠깐 기다렸다가 상태가 괜찮아지면 다시 연결을 시도한다.

즉,

  • 무의미한 호출을 줄이고

  • 전체 장애로 번지는 걸 막는다

언제 쓰면 좋을까?

  • 외부 API가 불안정할 때

  • 느린 응답 때문에 전체 서비스가 영향을 받을 때

  • 일단 멈추고 상태를 본 다음 다시 시도하고 싶을 때

정리하자면

Circuit Breaker는 지속적인 실패를 빠르게 차단하고, 전체 시스템을 지키는 안전장치다.

2. 세상엔 3가지 상태가 있다

Closed, Open, Half-Open

서킷 브레이커는 단순히 차단하고 말자가 아니다. 상태를 보면서 똑똑하게 판단한다. 크게 보면 3가지 상태가 있다.

Closed - 정상 상태

  • 모든 요청을 그대로 보낸다

  • 실패율을 내부적으로 계속 기록한다

  • 실패율이 임계치를 넘으면 Open 상태로 전환된다

Open - 요청 차단

  • 모든 요청을 즉시 거절한다

  • 외부 시스템에 부담을 주지 않기 위해 차단하는 상태

  • 일정 시간 동안 이 상태를 유지한다

Half-Open - 테스트 상태

  • 제한된 수의 요청만 보낸다

  • 성공률이 높으면 Closed로 전환

  • 실패가 반복되면 다시 Open 상태로 돌아간다

3. 세팅 잘못하면 진짜 터짐

CircuitBreakerConfig 뜯어보기

서킷 브레이커는 설정값에 따라 민감하게 반응하기도 하고, 너무 무뎌지기도 한다. 핵심 설정 몇 가지만 알아두면 된다.

주요 설정값

failureRateThreshold

  • 실패율 임계값(%)

  • 이 값을 넘으면 Closed → Open 상태로 전환

  • ex) 50이면, 최근 요청 중 절반이 실패하면 차단 시작

slidingWindowType

  • 실패율 계산 방식

  • COUNT_BASED: 고정된 요청 개수 기준 (ex. 최근 100건)

  • TIME_BASED: 일정 시간 기준 (ex. 최근 10초)

slidingWindowSize

  • 실패율을 계산할 때 참고할 요청 수나 시간

  • 위 slidingWindowType에 따라 의미가 달라짐

minimumNumberOfCalls

  • 실패율 계산을 시작하기 위한 최소 호출 수

  • 너무 적은 샘플로 판단하지 않기 위한 값

waitDurationInOpenState

  • Open 상태에서 Half-Open으로 전환되기까지 대기 시간

  • 너무 짧으면 자꾸 호출하고, 너무 길면 복구가 늦어진다

permittedNumberOfCallsInHalfOpenState

  • Half-Open 상태에서 허용할 테스트 요청 수

  • 성공하면 Closed, 실패하면 다시 Open

요약하면

설정값
설명

failureRateThreshold

실패율 기준 (%)

slidingWindowType, Size

실패율 계산 방식과 범위

minimumNumberOfCalls

판단을 시작하기 위한 최소 호출 수

waitDurationInOpenState

Open 상태 유지 시간

permittedNumberOfCallsInHalfOpenState

Half-Open에서 테스트할 호출 수

아래는 샘플로 작성해본 예시다.

@Bean
public CircuitBreakerConfigCustomizer rssFetcherCircuit() {
    return CircuitBreakerConfigCustomizer.of("rssFetcher", builder -> builder
            .slidingWindowType(SlidingWindowType.COUNT_BASED)
            .failureRateThreshold(50)
            .slowCallRateThreshold(100)
            .slowCallDurationThreshold(Duration.ofSeconds(2))
            .minimumNumberOfCalls(5)
            .slidingWindowSize(10)
            .waitDurationInOpenState(Duration.ofSeconds(5))
            .permittedNumberOfCallsInHalfOpenState(2)
    );
}
설정 항목
값
설명

failureRateThreshold

50%

실패율이 50% 이상이면 Open 상태 전환

slidingWindowType

COUNT_BASED

최근 호출 개수 기준으로 실패율 계산

slidingWindowSize

10

최근 10번 호출을 기준으로 상태 판단

minimumNumberOfCalls

5

최소 5번 호출되기 전까지는 평가하지 않음

서킷 브레이커는 잘 써야 도움이 된다. 너무 민감하게 세팅하면 오히려 정상 서비스도 막는다.

4. 한 줄이면 끝남

사용 방법 완전 정복

서킷 브레이커는 코드에서 직접 쓰거나, 애노테이션으로 간단하게 붙일 수 있다.

1. @CircuitBreaker 애노테이션 (Spring Boot)

@CircuitBreaker(name = "rssFetcher", fallbackMethod = "fallback")
public String callExternalApi() {
    return restTemplate.getForObject("<https://external.api>", String.class);
}

public String fallback(Exception e) {
    return "fallback response";
}
  • name은 설정에 등록한 이름

  • fallbackMethod는 실패했을 때 대체할 메서드

2. 함수 데코레이션 방식 (Java + Resilience4j Core)

CircuitBreaker cb = CircuitBreaker.ofDefaults("myApi");

Supplier<String> decorated = CircuitBreaker
    .decorateSupplier(cb, () -> externalService.call());

Try<String> result = Try.ofSupplier(decorated)
    .recover(throwable -> "fallback");
  • 함수형 스타일로 wrapping

  • 실패 처리 흐름을 recover()로 지정 가능

복잡해 보이지만, 결국 "실패하면 끊고, 대체할 방법을 미리 준비해두자" 가 핵심이다.

5. 망했을 때 플랜 B가 있어야지

Retry, TimeLimiter, Fallback

서킷 브레이커는 실패를 감지하고 차단하는 데 특화돼 있다. 하지만 그것만으로는 부족할 수 있다. Retry, TimeLimiter, Fallback 같은 도구들과 함께 써야 진짜 유용해진다.

1. Retry

  • 일시적인 오류는 다시 시도해서 극복

  • 예: 타임아웃, 네트워크 불안정

2. TimeLimiter

  • 너무 오래 걸리는 요청은 끊어버림

  • 예: 응답 없는 외부 API에 대한 방어

3. Fallback

  • 실패했을 때 대체 로직을 실행

  • 예: 캐시 데이터로 응답, 기본 메시지 반환 등

실행 순서 요약

  1. Retry: 몇 번 더 시도해보고

  2. TimeLimiter: 일정 시간 넘기면 끊고

  3. CircuitBreaker: 실패가 쌓이면 차단하고

  4. Fallback: 그래도 안 되면 기본값 반환

실패를 단순히 막는 게 아니라, 어떻게 대응할지를 미리 설계해두는 것이 중요하다.

6. 죽었는지 살았는지 어떻게 알아?

모니터링과 메트릭 확인법

서킷 브레이커는 상태 기반으로 동작하기 때문에 지금 어떤 상태인지, 얼마나 실패했는지 모르면 제대로 운영하기 어렵다. 그래서 모니터링이 꼭 필요하다.

상태 확인 방법

1. Actuator 엔드포인트

Spring Boot를 사용하면 /actuator/circuitbreakers 에서 현재 상태와 통계 정보를 바로 확인할 수 있다.

  • 상태 (Closed / Open / Half-Open)

  • 호출 수, 실패율, 마지막 상태 변경 시각 등

management:
  endpoints:
    web:
      exposure:
        include: circuitbreakers
  • http://localhost:8080/actuator/health

  • http://localhost:8080/actuator/metrics

  • http://localhost:8080/actuator/metrics/resilience4j.circuitbreaker.state

2. Prometheus + Grafana

  • Resilience4j는 메트릭을 Micrometer로 노출

  • Prometheus로 수집하고, Grafana로 시각화

  • 서킷 브레이커별 호출 수, 실패율, 전이 횟수 등을 대시보드로 볼 수 있다

3. CircuitBreakerRegistry

Java 코드에서 직접 상태나 통계를 확인하고 싶을 때

CircuitBreaker cb = registry.circuitBreaker("myApi");
cb.getMetrics().getFailureRate(); // 실패율
cb.getState(); // 현재 상태

주의할 점

  • Open 상태가 지나치게 자주 발생하면 설정을 다시 봐야 한다

  • Half-Open 이후 다시 Open으로 돌아간다면 → 외부 시스템이 아직 회복되지 않았다는 의미

  • 장애 대응 과정에서 Fallback으로 대체된 경우도 로그로 추적해야 한다

모니터링이 없다면 서킷 브레이커는 그냥 호출이 안 되는 이유를 모르는 블랙박스가 된다.

아직 감이 안온다면 다음 예시로 확인해보자.

  • 예시 코드 https://github.com/signature-hyeon/resilience4j-circuitbreaker

  • 공식 문서 https://github.com/resilience4j/resilience4j > resilience4j-circuitbreaker

waitDurationInOpenState

5초

Open 상태에서 5초 후 Half-Open 전환

permittedNumberOfCallsInHalfOpenState

2

Half-Open 상태에서 2번 테스트 호출 허용

slowCallDurationThreshold

2초

2초 넘으면 느린 호출로 간주

slowCallRateThreshold

100%

느린 호출 비율이 100%면 실패로 처리