서킷 브레이커 (Resilience4j)

2026. 6. 27. 12:58·MSA

1. 서킷 브레이커

마이크로서비스 간의 호출 실패를 감지하고 시스템의 전체적인 안정성을 유지하는 패턴으로 외부 서비스 호출 실패 시 빠른 실패를 통해 장애를 격리하여 시스템의 다른 부분에 영향을 주지 않도록 한다.

상태 변화: 클로즈드 -> 오픈 -> 하프-오픈

 

2. Resilience4j

서킷 브레이커 라이브러리로, 서비스 간 호출 실패를 감지하고 시스템의 안정성을 유지한다.

다양한 서킷 브레이커 기능을 제공하며, 장애 격리 및 빠른 실패를 통해 복원력을 높인다.

 

2-1. Resilience4j의 주요 특징

서킷 브레이커 상태

- 클로즈드(Closed)

  • 기본 상태(Default)로 모든 요청을 통과시킨다.
  • 호출 실패 시, 실패 카운터가 증가한다.
  • 실패율이 설정된 임계값(설정 값)을 초과하면 서킷 브레이커가 오픈 상태로 전환된다.

- 오픈(Open)

  • 오픈 상태로 전환 시, 모든 요청을 즉시 실패로 처리한다.
  • 오픈 상태에서 요청이 들어오면 바로 에러 응답을 반환한다.
  • 설정된 대기 시간이 지난 후, 서킷 브레이커는 하프-오픈 상태로 전환한다.

- 하프-오픈(Half-Open)

  • 하프-오픈 상태에서는 제한된 수의 요청을 허용하여 시스템이 정상 상태로 복구되었는지 확인한다.
  • 요청이 성공하면 서킷브레이커는 기본 상태인 클로즈드(Closed) 상태로 전환된다.
  • 요청이 실패하면 다시 오픈(Open) 상태로 변환한다.
  • 예를 들어, 하프-오픈 상태에서 3개의 요청을 허용하여, 3개의 요청을 모두 성공해야 클로즈드 상태로 전환된다. 이 중 하나라도 실패하면 오픈 상태로 전환된다.

- Fallback이란?

  • 외부 서비스 호출 실패 시 대체 로직을 제공하여 시스템 안정성을 확보하는 방법

- Fallback의 장점

  • 시스템의 안정성을 높이고, 장애가 발생해도 사용자에게 일정한 응답을 제공할 수 있다.
  • 장애가 다른 서비스에 전파되는 것을 방지한다.

Fallback 설정

@Service
public class MyService {

    @CircuitBreaker(name = "myService", fallbackMethod = "fallbackMethod") 
    // 에러 발생 시, fallbackMethod로 이동
    public String myMethod() {
        // 외부 서비스 호출
        return externalService.call();
    }

    public String fallbackMethod(Throwable t) {
        return "Fallback response";
    }
}

 

3. Resilience4j Dashboard 사용

  • Prometheus와 Grafana를 사용하여 Resilience4j 서킷 브레이커의 상태를 실시간으로 모니터링을 할 수 있다.
  • Prometheus를 통해 수집된 메트릭을 Grafana 대시보드에서 시각화할 수 있습니다.

 

4. 서킷 브레이커(Resilience4j) 구현

build.gradle

dependencies {
    implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
    implementation 'org.springframework.boot:spring-boot-starter-aop'

    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

 

products/Product.java

@Data
@AllArgsConstructor
@NoArgsConstructor

public class Product {

    private String id;
    private String title;
}

 

products/ ProductController.java

@RestController
@RequiredArgsConstructor

public class ProductController {
    private final ProductService productService;

    @GetMapping("/product/{id}")
    public Product getProduct(@PathVariable String id) {
        return productService.getProductDetails(id);
    }
}

 

products/ ProductService.java(기본설정)

@Service
@RequiredArgsConstructor
public class ProductService {

    @CircuitBreaker(name = "productService", fallbackMethod = "fallbackGetProductDetails")
    public Product getProductDetails(String productId) {

        if("111".equals(productId)){
            throw new RuntimeException("Empty response body");
        }

        return new Product(productId, "Sample Product");
    }

    public Product fallbackGetProductDetails(String productId, Throwable t) {
        return new Product(productId, "Fallback Product");
    }
}

 

products/ ProductService.java (실행 결과를 확인하기 위한 코드)

@Service
@RequiredArgsConstructor
public class ProductService {

    private final Logger log = LoggerFactory.getLogger(getClass());
    private final CircuitBreakerRegistry circuitBreakerRegistry;

    @PostConstruct
    public void registerEventListener() {
        circuitBreakerRegistry.circuitBreaker("productService").getEventPublisher()
                .onStateTransition(event -> log.info("#######CircuitBreaker State Transition: {}", event)) // 상태 전환 이벤트 리스너
                .onFailureRateExceeded(event -> log.info("#######CircuitBreaker Failure Rate Exceeded: {}", event)) // 실패율 초과 이벤트 리스너
                .onCallNotPermitted(event -> log.info("#######CircuitBreaker Call Not Permitted: {}", event)) // 호출 차단 이벤트 리스너
                .onError(event -> log.info("#######CircuitBreaker Error: {}", event)); // 오류 발생 이벤트 리스너
    }


    @CircuitBreaker(name = "productService", fallbackMethod = "fallbackGetProductDetails")
    public Product getProductDetails(String productId) {
        log.info("###Fetching product details for productId: {}", productId);
        if ("111".equals(productId)) {
            log.warn("###Received empty body for productId: {}", productId);
            throw new RuntimeException("Empty response body");
        }
        return new Product(
                productId,
                "Sample Product"
        );
    }

    public Product fallbackGetProductDetails(String productId, Throwable t) {
        log.error("####Fallback triggered for productId: {} due to: {}", productId, t.getMessage());
        return new Product(
                productId,
                "Fallback Product"
        );
    }
 }

 

resources/application.yml

spring:
  application:
    name: sample

server:
  port: 19090

resilience4j:
  circuitbreaker:
    configs:
      default:  # 기본 구성 이름
        registerHealthIndicator: true  # 애플리케이션의 헬스 체크에 서킷 브레이커 상태를 추가하여 모니터링 가능
        # 서킷 브레이커가 동작할 때 사용할 슬라이딩 윈도우의 타입을 설정
        # COUNT_BASED: 마지막 N번의 호출 결과를 기반으로 상태를 결정
        # TIME_BASED: 마지막 N초 동안의 호출 결과를 기반으로 상태를 결정
        slidingWindowType: COUNT_BASED  # 슬라이딩 윈도우의 타입을 호출 수 기반(COUNT_BASED)으로 설정
        # 슬라이딩 윈도우의 크기를 설정
        # COUNT_BASED일 경우: 최근 N번의 호출을 저장
        # TIME_BASED일 경우: 최근 N초 동안의 호출을 저장
        slidingWindowSize: 5  # 슬라이딩 윈도우의 크기를 5번의 호출로 설정
        minimumNumberOfCalls: 5  # 서킷 브레이커가 동작하기 위해 필요한 최소한의 호출 수를 5로 설정
        slowCallRateThreshold: 100  # 느린 호출의 비율이 이 임계값(100%)을 초과하면 서킷 브레이커가 동작
        slowCallDurationThreshold: 60000  # 느린 호출의 기준 시간(밀리초)으로, 60초 이상 걸리면 느린 호출로 간주
        failureRateThreshold: 50  # 실패율이 이 임계값(50%)을 초과하면 서킷 브레이커가 동작
        permittedNumberOfCallsInHalfOpenState: 3  # 서킷 브레이커가 Half-open 상태에서 허용하는 최대 호출 수를 3으로 설정
        # 서킷 브레이커가 Open 상태에서 Half-open 상태로 전환되기 전에 기다리는 시간
        waitDurationInOpenState: 20s  # Open 상태에서 Half-open 상태로 전환되기 전에 대기하는 시간을 20초로 설정

management:
  endpoints:
    web:
      exposure:
        include: prometheus
  prometheus:
    metrics:
      export:
        enabled: true

5. 실행 결과

http://localhost:19090/product/1 등 111을 제외한 다른 숫자 URL을 요청했을 때

 

http://localhost:19090/product/111 을 3번 요청하니

 

맨 아랫줄을 보면 Closed 모드로 전환되었다.

총합 5번 중 실패율이 50%를 넘겼기 때문에 CLOSED에서 OPEN으로 전환된 것이다.

 

 

서킷이 OPEN이 되자마자, 이후 들어오는 요청(product/1, product/2 등)은 실제 백엔드 메서드를 호출하지도 않고 Call Not Permitted 에러를 뱉으며 즉시 Fallback 처리된다.

 

이 상태로 정확히 20초(waitDurationInOpenState: 20s)가 흐릅니다.

 

20초가 지나자 서킷 브레이커가 시스템을 테스트해 보기 위해 스스로 HALF-OPEN 모드로 전환

 

 

이 상태에서 설정해둔 횟수(3번)만큼 요청을 성공시키면, 시스템이 안정되었다고 판단하여 다시 기본 값인 클로즈드(Closed) 모드로 돌아간다.

'MSA' 카테고리의 다른 글

보안 구성  (0) 2026.06.28
API 게이트웨이 (Spring Cloud Gateway)  (0) 2026.06.27
로드 밸런싱  (0) 2026.06.26
서비스 디스커버리 (Eureka) 와 Server/Client 설정 방법  (0) 2026.06.26
Spring Cloud란 무엇일까?  (0) 2026.06.25
'MSA' 카테고리의 다른 글
  • 보안 구성
  • API 게이트웨이 (Spring Cloud Gateway)
  • 로드 밸런싱
  • 서비스 디스커버리 (Eureka) 와 Server/Client 설정 방법
taeyoon2
taeyoon2
taeyoon2 님의 블로그 입니다.
  • taeyoon2
    taeyoon2 님의 블로그
    taeyoon2
  • 전체
    오늘
    어제
    • 분류 전체보기 (36)
      • 심화_AI를 활용한 백엔드 아키텍처 심화 과정 (24)
      • MSA (11)
      • Study (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    보안구성
    심화_AI를 활용한 백엔드 아키텍처 심화 과정
    분산추적
    서킷 브레이커
    Zipkin
    Config
    JWT
    api gateway
    RabbitMQ
    MSA
    spring cloud config
    Resilience4j
    사전캠프
    내일배움캠프
    OAuth2
    spring cloud
    이벤트 드리븐
    til
    내일배움캠프 #사전캠프 #til
    로드 밸런싱
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
taeyoon2
서킷 브레이커 (Resilience4j)
상단으로

티스토리툴바