🔥스파르타 TIL (MSA)

Spring Cloud 란? (3) - 서킷 브레이커

승승장규 2025. 3. 8. 16:12

마이크로서비스 간의 호출 실패를 감지하고 시스템의 전체적인 안정성을 유지하는 패턴으로

외부 서비스 호출 실패 시 빠른 실패를 통해 장애를 격리하고, 시스템의 다른 부분에 영향을 주지 않도록 한다.

 

 

Spring Cloud 란? (1)

마이크로서비스 개발을 위해 다양한 도구와 서비스를 제공하는 스프링 프레임워크의 확장이며, 마이크로서비스 아키텍처를 쉽게 구현하고 운영할 수 있도록 도와준다. 주요 기능 ▼서비스 등

seungg8361.tistory.com

 

 

 

Resilience4j 주요 특징 ▼

  • 서킷 브레이커의 상태인 클로즈드, 오픈, 하프-오픈 상태를 통해 호출 실패를 관리한다.
  • 클로즈드 (Closed) : 기본 상태, 모든 요청을 통과시킨다. 호출이 실패하면 실패 카운터가 증가하고, 실패율이 설정된 임계값을 초과하면 서킷 브레이커가 Open 상태로 전환된다. ex) 5번 호출 중 3번이 실패하여 임계값을 초과하면 Open 상태로 전환.
  • 오픈 (Open) : 모든 요청을 즉시 실패로 처리하고 에러 응답을 반환한다. 설정된 대기 시간이 지난 후, Half-Open 상태로 전환된다. ex) Open 상태로 전환되고 20초 동안 모든 요청이 차단된다.
  • 하프-오픈 (Half-Open) : Open 상태에서 대기 시간이 지난 상태로, 제한된 수의 요청을 허용하여 시스템이 정상 상태로 복귀되었는지 확인한다. 요청이 성공하면 Closed 상태로 전환되고, 다시 실패하면 Open 상태로 전환된다. ex) Half-Open 상태에서 3개의 요청을 허용하고, 모두 성공하면 Closed 상태로 전환되지만, 하나라도 실패하면 다시 Open 상태로 전환된다.
  • Fallback : 호출 실패 시 대체 로직을 제공하여 시스템 안정성 확보. 장애 사항이 발생했을 때 실행된 메서드를 통해 어디서, 어떤 에러가 발생 했는지 알려줄 수 있다. ex) A() 메서드가 실패하면 B() 메서드를 실행시킴.
  • 모니터링 : 서킷 브레이커 상태를 모니터링하고 관리할 수 있는 다양한 도구 제공

 

 

 

Resilience4j를 사용해 보자

 

build.gradle

dependencies {
	// SpringBoot 버전에 따라 달라짐
	implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
	implementation 'org.springframework.boot:spring-boot-starter-aop'
}

 

application.yml

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초로 설정

 

 

서킷 브레이커 코드를 작성해서 직접 확인 해보자

@GetMapping("/product/{id}")
    public Product getProduct(@PathVariable("id") String id) {
        return productService.getProductDetails(id);
    }
// 이 함수가 정상적으로 동작하지 않을 때 fallbackMethod 에 설정한 메서드가 발동
    @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 : " + productId);
    }

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

 

 

http://localhost:포트번호/product/1

{
  "id": "1",
  "title": "Sample Product : 1"
}

 

http://localhost:포트번호/product/111

{
  "id": "111",
  "title": "Fallback Product : 111"
}

 

처음에는 정상적으로 접근을 해보았다.

이후 예외처리 시킨 "111"로 접근을 시도하면 CircuitBreak에서 설정한 fallbackMethod가 실행되는 것을 볼 수 있다. 하지만 아직 임계값을 초과하지 않았으므로 Open 상태로 변하지 않았다.

이후 잘못된 요청이 계속되어서 임계값을 넘어가면 기존의 Closed 상태에서 Open 상태로 변하게 된다. 

Open 상태에서는 정삭적인 접근인 "1"로 접근해도 실패처리가 되는 모습을 볼 수 있다. 이후 일정 시간이 지난 후 다시 정상적인 요청을 보내면 Open 상태에서 Half-Open 상태로 돌아오는 것을 볼 수 있다. 이후 요청은 Closed 상태에서 받게된다.

 

 

 

추가적으로 간단하게 이 정보를 시각화 해서 확인을 해보자

/actuator/prometheus 엔드포인트로 접속해서 확인해보면 굉장히 길게 정보가 나타나는 것을 볼 수 있다.

그 중에서 resilience4j 정보를 확인해 보면 기본적으로 closed 상태였지만 요청이 계속 실패하면 open에 1.0이 생기고,

일정 시간이 지나고 정상적인 요청을 수행하면 half_open에 1.0이 생기는 것을 볼 수 있다.

resilience4j_circuitbreaker_state{name="productService",state="closed"} 1.0
resilience4j_circuitbreaker_state{name="productService",state="disabled"} 0.0
resilience4j_circuitbreaker_state{name="productService",state="forced_open"} 0.0
resilience4j_circuitbreaker_state{name="productService",state="half_open"} 0.0
resilience4j_circuitbreaker_state{name="productService",state="metrics_only"} 0.0
resilience4j_circuitbreaker_state{name="productService",state="open"} 0.0

 

이런 시각화 된 정보를 보고싶다면 아래 설정을 추가해주면 된다.

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-actuator'

 

application.yml

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

 

 

 

마무리로 이와 같이 @CircuitBreaker 어노테이션에 fallbackMethod를 설정해 놓으면,

해당 메서드에 에러가 발생했을 때 설정된 메서드를 호출하게 되고, fallbackMethod를 계속해서 호출되면

Closed 상태에서 Open 상태로 변하게 되면서 fallbackMethod만 실행되게 된다.

이후 일정 시간이 지나면 Open 상태에서 Half-Open 상태로 변하면서 기존 함수를 실행하게 되고,

문제가 없다면 정상적으로 실행되게 된다.