본문 바로가기
System Engineering/Spring Boot java (스프링 부트 자바)

서킷 브레이커 패턴과 Java Spring + Resilience4j + actuator 이용한 구현

by 코딩하는 동현 2025. 8. 21.

Circuit Breaker 아키텍처 개요

Circuit Breaker는 전기 회로의 서킷 브레이커처럼, 서비스 간 호출에서 장애가 반복될 때 서킷(회로)을 "열어(Open)" 더 이상 장애 위험이 있는 서비스 호출을 차단하고, 일정 시간이 지난 후에 제한적으로 호출을 다시 허용하는 방식으로 시스템 과부하와 장애 확산을 방지하는 패턴입니다.

  • Closed(닫힘): 정상 상태로 모든 요청이 서비스로 전달됨.
  • Open(열림): 지정한 실패율 임계치를 초과하면 서킷을 열어 서비스 호출 차단.
  • Half-Open(반열림): 일정 시간 후 일부 요청을 시도해 장애 복구 여부 확인.

Circuit Breaker 주요 기능과 설정 파라미터

Resilience4j Circuit Breaker 주요 기능 및 설정 파라미터 하나하나에 대해 상세히 설명드리겠습니다.

1. slidingWindowType

  • 슬라이딩 윈도우 방식으로 실패율을 계산하는 기준입니다.
  • COUNT_BASED: 최근 호출 횟수 기준으로 윈도우 크기를 정함. 예를 들어, 최근 10번 호출 결과로 실패율 계산.
  • TIME_BASED: 최근 일정 시간(예: 1분) 동안의 호출 결과로 실패율 계산.

2. slidingWindowSize

  • 슬라이딩 윈도우 크기입니다.
  • COUNT_BASED일 경우, 최근 몇 번의 호출에 대해 실패율과 상태 변화를 판단할지 결정합니다.
  • 예: 10으로 설정 시, 최신 10회 호출 결과만 보고 실패율을 계산합니다.

3. failureRateThreshold

  • 실패율의 임계값(%)을 의미합니다.
  • 슬라이딩 윈도우에서 계산된 실패율이 이 값 이상이면 서킷브레이커가 OPEN 상태로 전환되어 더 이상 해당 서비스 호출을 막습니다.
  • 예: 70%로 설정하면, 10회 중 7회 이상 실패 시 서킷이 열립니다.

4. minimumNumberOfCalls

  • Circuit Breaker가 상태를 판단하기 위해 최소한으로 필요한 호출 수입니다.
  • 이 수만큼 호출이 누적되기 전까지는 서킷브레이커가 CLOSE 상태를 유지해 정상 동작으로 간주합니다.
  • 호출 수가 적을 때 너무 빠른 상태 전환으로 인한 오작동 방지용입니다.

5. waitDurationInOpenState

  • 서킷브레이커가 OPEN 상태에 있을 때, 다음 상태인 HALF_OPEN 상태 전환을 위해 기다리는 시간입니다.
  • 보통 일정 시간 동안 장애 상태를 유지하다가, 일정 시간이 지나면 장애가 해소되었는지 테스트 차원에서 일부 호출을 허용합니다.
  • 예: 10초로 설정하면 OPEN 상태로 10초 동안 유지 후 HALF_OPEN 상태로 변경 시도.

6. permittedNumberOfCallsInHalfOpenState

  • HALF_OPEN 상태에서 허용할 최대 호출 수입니다.
  • 이 수만큼 호출을 시험용으로 허용하여 성공률을 평가합니다.
  • 이 호출들이 성공하면 서킷브레이커는 CLOSE 상태로 복귀하고, 실패 시 다시 OPEN 상태로 돌아갑니다.

7. automaticTransitionFromOpenToHalfOpenEnabled

  • OPEN 상태에서 자동으로 HALF_OPEN 상태로 전환할지 여부입니다.
  • true로 설정하면, waitDurationInOpenState 시간이 지나면 자동으로 전환됩니다.
  • false는 수동 전환 또는 외부 이벤트에 의해 상태 전환을 할 때 사용.

추가 중요한 설정

  1. slowCallDurationThreshold
    • 호출이 이 시간 이상 걸리면, ‘느린 호출(slow call)’로 간주합니다.
    • 예: 3000ms (3초) 이상 걸리면 실패처럼 판단할 수 있습니다.
  2. slowCallRateThreshold
    • 슬라이딩 윈도우 내 ‘느린 호출’ 비율 임계값(%)입니다.
    • 이 비율 이상이면 서킷브레이커가 OPEN 상태로 전환될 수 있습니다.
  3. recordExceptions
    • 장애로 기록할 예외를 지정합니다. 지정한 예외가 발생하면 호출 실패로 기록됩니다.
  4. ignoreExceptions
    • 실패로 간주하지 않을 예외입니다. 이 예외가 발생해도 호출 성공으로 간주합니다.
  5. eventConsumerBufferSize
    • 내부적으로 상태 변경 등 이벤트를 저장하는 버퍼 크기입니다. Actuator 등에서 이벤트 모니터링 시 활용됩니다.

Gradle 의존성 설정 방법

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'io.github.resilience4j:resilience4j-spring-boot2:1.7.0'
    implementation 'io.github.resilience4j:resilience4j-all:1.7.0'
    // 선택적으로 각 패턴별 모듈(resilience4j-circuitbreaker, resilience4j-retry, 등)만 추가해도 됨
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

  • 최신 버전은 공식 문서에 따라 변경될 수 있음.
 

Introduction

Resilience4j is a lightweight fault tolerance library designed for functional programming. Resilience4j provides higher-order functions (decorators) to enhance any functional interface, lambda expression or method reference with a Circuit Breaker, Rate Lim

resilience4j.readme.io


예시 YAML 설정 (application.yml)

resilience4j.circuitbreaker:
  configs:
    default:
      slidingWindowType: COUNT_BASED
      minimumNumberOfCalls: 7
      slidingWindowSize: 10
      waitDurationInOpenState: 10s
      failureRateThreshold: 40
      slowCallDurationThreshold: 3000
      slowCallRateThreshold: 60
      permittedNumberOfCallsInHalfOpenState: 5
      automaticTransitionFromOpenToHalfOpenEnabled: true
      eventConsumerBufferSize: 10
      recordExceptions:
        - com.example.resilience4jdemo.exception.RecordException
      ignoreExceptions:
        - com.example.resilience4jdemo.exception.IgnoreException
  instances:
    simpleCircuitBreakerConfig:
      baseConfig: default

management.endpoints.web.exposure.include: '*'
management.endpoint.health.show-details: always

management.health.diskspace.enabled: false
management.health.circuitbreakers.enabled: true

management.metrics.tags.application: ${spring.application.name}
management.metrics.distribution.percentiles-histogram.http.server.requests: true
management.metrics.distribution.percentiles-histogram.resilience4j.circuitbreaker.calls: true

주요 파라미터 설명

  • minimumNumberOfCalls: 7 → 최소 7번 호출은 무조건 정상 호출로 간주(초기 데이터 부정확성 보완).
  • slidingWindowSize: 10 → 이후 최신 10회 호출 기록으로 실패율 계산.
  • failureRateThreshold: 40 → 실패율 40% 이상 시 서킷 열림(Open).
  • waitDurationInOpenState: 10s → Open 상태에서 10초 기다렸다가 Half-Open 시도.
  • permittedNumberOfCallsInHalfOpenState: 5 → Half-Open 상태 시 5번 재시도 후 상태 결정.
  • automaticTransitionFromOpenToHalfOpenEnabled: true → 자동으로 Open → Half-Open 상태 전환.
  • recordExceptions 및 ignoreExceptions → 어떤 예외를 실패로 기록할지, 무시할지를 정함.

CircuitBreaker 서비스 예제 코드

@Slf4j
@Service
public class CircuitBreakerService {

    private static final String SIMPLE_CIRCUIT_BREAKER_CONFIG = "simpleCircuitBreakerConfig";

    @CircuitBreaker(name = SIMPLE_CIRCUIT_BREAKER_CONFIG, fallbackMethod = "fallback")
    public String process(String param) throws InterruptedException {
        return callAnotherServer(param);
    }

    // 예외 유형에 따른 개별 fallback 처리
    private String fallback(String param, RecordException ex) {
        log.info("RecordException fallback! your request is " + param);
        return "Recovered: " + ex.toString();
    }

    private String fallback(String param, IgnoreException ex) {
        log.info("IgnoreException fallback! your request is " + param);
        return "Recovered: " + ex.toString();
    }

    private String fallback(String param, CallNotPermittedException ex) {
        log.info("CallNotPermittedException fallback! your request is " + param);
        return "Recovered: " + ex.toString();
    }

    private String callAnotherServer(String param) throws InterruptedException {
        if ("a".equals(param))
            throw new RecordException("record exception");
        else if ("b".equals(param))
            throw new IgnoreException("ignore exception");
        else if ("c".equals(param)) // 3초 이상 걸리는 경우도 실패로 간주 (slow call)
            Thread.sleep(4000);

        return param;
    }
}

 

 

 


Spring Actuator와 연동

  • Actuator를 통해 Circuit Breaker 상태, 호출 통계, 이벤트 등을 실시간 모니터링할 수 있습니다.
  • Actuator 설정에서 management.health.circuitbreakers.enabled를 true로 활성화하면 /actuator/health/circuitbreakers 엔드포인트에서 상태 확인 가능.
  • /actuator/circuitbreakers에서 서킷 브레이커 관련 메트릭과 상태들을 상세하게 노출합니다.
  • 이와 함께 메트릭(histogram, percentile)도 활성화하여 퍼포먼스 모니터링 강화.

Resilience4j Circuit Breaker와 스프링 Actuator 통합 실습

Actuator 관련 설정 활성화 (application.yml)

먼저 Actuator 엔드포인트들을 활성화하여 Circuit Breaker 관련 상태 및 제어가 가능하도록 설정합니다.

management.endpoints.web.exposure.include: '*'
management.endpoint.health.show-details: always
management.health.circuitbreakers.enabled: true
management.health.diskspace.enabled: false

management.metrics.tags.application: ${spring.application.name}
management.metrics.distribution.percentiles-histogram.http.server.requests: true
management.metrics.distribution.percentiles-histogram.resilience4j.circuitbreaker.calls: true

Circuit Breaker 인스턴스 및 설정 정의 (application.yml)

resilience4j.circuitbreaker:
  configs:
    default:
      slidingWindowType: COUNT_BASED
      minimumNumberOfCalls: 7                                   # 최소 7번까지는 무조건 CLOSE로 가정하고 호출한다.
      slidingWindowSize: 10                                     # (minimumNumberOfCalls 이후로는) 10개의 요청을 기준으로 판단한다.
      waitDurationInOpenState: 10s                              # OPEN 상태에서 HALF_OPEN으로 가려면 얼마나 기다릴 것인가?

      failureRateThreshold: 40                                  # slidingWindowSize 중 몇 %가 recordException이면 OPEN으로 만들 것인가?

      slowCallDurationThreshold: 3000                           # 몇 ms 동안 요청이 처리되지 않으면 실패로 간주할 것인가?
      slowCallRateThreshold: 60                                 # slidingWindowSize 중 몇 %가 slowCall이면 OPEN으로 만들 것인가?

      permittedNumberOfCallsInHalfOpenState: 5                  # HALF_OPEN 상태에서 5번까지는 CLOSE로 가기위해 호출한다.
      automaticTransitionFromOpenToHalfOpenEnabled: true        # OPEN 상태에서 자동으로 HALF_OPEN으로 갈 것인가?

      eventConsumerBufferSize: 10                               # actuator를 위한 이벤트 버퍼 사이즈

      recordExceptions:
        - com.example.resilience4jdemo.exception.RecordException
      ignoreExceptions:
        - com.example.resilience4jdemo.exception.IgnoreException
  instances:
    simpleCircuitBreakerConfig:
      baseConfig: default

Resilience4j Circuit Breaker 설정을 작성해 인스턴스와 상세 파라미터를 지정합니다.

서비스 코드에 @CircuitBreaker 적용

package com.example.resilience4jdemo.e_circuitbreaker_fallback;

import com.example.resilience4jdemo.exception.IgnoreException;
import com.example.resilience4jdemo.exception.RecordException;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class CircuitBreakerService {

    private static final String SIMPLE_CIRCUIT_BREAKER_CONFIG = "simpleCircuitBreakerConfig";

    @CircuitBreaker(name = SIMPLE_CIRCUIT_BREAKER_CONFIG, fallbackMethod = "fallback")
    public String process(String param) throws InterruptedException {
        return callAnotherServer(param);
    }

    private String fallback(String param, RecordException ex) {
        log.info("RecordException fallback! your request is " + param);
        return "Recovered: " + ex.toString();
    }

    private String fallback(String param, IgnoreException ex) {
        log.info("IgnoreException fallback! your request is " + param);
        return "Recovered: " + ex.toString();
    }

    private String fallback(String param, CallNotPermittedException ex) {
        log.info("CallNotPermittedException fallback! your request is " + param);
        return "Recovered: " + ex.toString();
    }

    private String callAnotherServer(String param) throws InterruptedException {
        if ("a".equals(param))
            throw new RecordException("record exception");
        else if ("b".equals(param))
            throw new IgnoreException("ignore exception");
        else if ("c".equals(param)) // 3초 이상 걸리는 경우도 실패로 간주
            Thread.sleep(4000);

        return param;
    }

};

Circuit Breaker를 적용할 메소드에 어노테이션과 fallback 메소드를 구현합니다.


1. 서킷브레이커 목록 조회

  • 애플리케이션에 정의된 모든 Circuit Breaker 인스턴스의 이름 목록을 확인합니다.
  • HTTP 요청: GET <http://localhost:8080/actuator/circuitbreakers>
  • 이 엔드포인트는 현재 애플리케이션에서 관리 중인 Circuit Breaker 이름을 리스트 형태로 보여줍니다.

2. 서킷브레이커 상태 강제 변경 (예: OPEN 상태로 강제 전환)

  • 개발자나 운영자가 수동으로 Circuit Breaker 상태를 변경하여 테스트하거나 장애 시나리오 시뮬레이션에 활용합니다.
  • HTTP 요청: POST <http://localhost:8080/actuator/circuitbreakers/{circuitBreakerName}>
  • 요청 바디(JSON 예시):
    • FORCE_OPEN: 강제로 서킷을 열어 호출 차단
    • FORCE_CLOSED: 강제로 서킷을 닫아 호출 허용
    • CLOSED, OPEN, HALF_OPEN 등의 상태도 직접 지정 가능
  • 이 명령을 보내면 해당 Circuit Breaker가 즉시 지정한 상태로 변경되어 동작하며, 서비스 호출 흐름에도 즉각적 영향을 줍니다.
  • 만약에 open으로 설정시, 자동으로 half open 되지 않습니다.
반응형

댓글