자바 디자인 패턴 용어 사전
실무 적용Decorators · 서킷 브레이커 · CircuitBreaker

Resilience4j

데코레이터를 API로 노출한 장애 대응 라이브러리. 중첩 순서가 장애 규모를 가른다.

Decorator 패턴을 API 표면에 그대로 노출한 장애 대응 라이브러리.

Supplier<Order> decorated = Decorators.ofSupplier(() -> client.fetch(id))
        .withBulkhead(Bulkhead.ofDefaults("order"))
        .withCircuitBreaker(CircuitBreaker.ofDefaults("order"))
        .withRetry(Retry.ofDefaults("order"))
        .decorate();

Order order = decorated.get();

문서화된 기본 중첩 순서

Retry ( CircuitBreaker ( RateLimiter ( TimeLimiter ( Bulkhead ( 실제 호출 ) ) ) ) )

Retry 가 가장 바깥, Bulkhead 가 가장 안쪽

이 순서가 장애를 가른다

  • Retry(CircuitBreaker(호출)) — ← 기본값
    • 서킷이 열려 있으면 즉시 실패 → 재시도도 즉시 실패
    • 재시도가 죽어 가는 서버를 다시 때리지 않는다 ✅

CircuitBreaker(Retry(호출))

  • 재시도 3번을 '한 번의 호출' 로 묶어서 서킷이 본다
  • 서킷 입장에서 실패 1건, 실제 트래픽은 3배
  • 장애 서버에 부하를 3배로 실어 나른다 ❌
  • 서킷이 열리기까지도 3배 오래 걸린다

순서는 resilience4j.retry.retryAspectOrder 같은 속성으로 바꿀 수 있다(값이 클수록 우선). 바꿀 수 있다는 것은 잘못 바꿀 수도 있다는 뜻이다 — 기본값을 건드릴 이유가 없으면 그대로 둔다.

각 데코레이터가 막는 것

  • CircuitBreaker — 실패가 임계치를 넘으면 호출 자체를 차단한다
    • 죽은 서버를 계속 때려 장애를 전파하는 것을 막는다
  • Retry — 일시적 실패를 흡수한다 (반드시 백오프와 함께)
  • RateLimiter — 내가 보내는 속도를 제한한다
  • TimeLimiter — 응답이 없을 때 무한정 기다리지 않는다
  • Bulkhead — 동시 실행 수를 제한한다
    • 한 외부 연동의 지연이 전체 스레드를 잠식하는 것을 막는다

쌓을 때의 실무 규칙

  • 바깥쪽에 둘 것 — 재시도 · 타임아웃 · 서킷 (호출 전체를 감싸야 의미가 있다)
  • 안쪽에 둘 것 — 동시 실행 제한 · 커넥션 확보 (자원에 가까울수록 정확하다)
  • 가장 바깥 — 로깅 · 메트릭

메트릭을 안쪽에 두면 재시도 3번이 3건으로 집계된다 바깥에 두면 1건으로 집계된다 — 무엇을 세고 싶은지에 따라 고른다

서킷 브레이커의 세 상태

  • CLOSED — 정상. 호출을 통과시키며 실패율을 집계한다

    • 실패율이 임계치를 넘으면 → OPEN
  • OPEN — 차단. 호출을 즉시 실패시킨다 (대상 서버를 아예 안 때린다)

    • 대기 시간이 지나면 → HALF_OPEN
  • HALF_OPEN — 탐색. 정해진 수만큼만 통과시켜 본다

    • 성공하면 → CLOSED, 실패하면 → OPEN
핵심 값
  slidingWindowSize          집계 창 크기
  failureRateThreshold       열리는 실패율 (기본 50%)
  waitDurationInOpenState    OPEN 유지 시간
  permittedNumberOfCallsInHalfOpenState  탐색 호출 수

무엇을 실패로 셀 것인가

기본적으로 모든 예외를 실패로 센다 — 이것이 흔한 함정이다

404 Not Found 나 400 Bad Request 가 예외로 올라오면 '서버는 멀쩡한데' 서킷이 열린다

  • recordExceptions / ignoreExceptions 로 구분한다
    • 비즈니스 예외는 무시하고, 타임아웃·5xx·연결 실패만 센다

폴백이 없으면 서킷은 반쪽이다

서킷이 열리면 호출이 즉시 실패한다 — 그다음이 정의돼 있어야 한다

  • 캐시된 이전 값을 준다
  • 기본값·빈 목록을 준다 (기능 일부만 죽는다)
  • 사용자에게 명확히 알린다

폴백 없이 서킷만 켜면 '빨리 실패하는 장애' 가 될 뿐이다

면접 함정

  • "재시도를 넣으면 안정적이다" → 백오프 없는 재시도는 장애를 증폭시킨다.
  • "서킷 브레이커만 있으면 된다" → 동시 실행 제한이 없으면 한 연동이 스레드를 전부 잠식한다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 실무 적용 심화 — 프레임워크 소스로 읽는 패턴