서킷 브레이커(CircuitBreaker) 적용하기

su_under·2024년 6월 4일
0
post-thumbnail
post-custom-banner

Resilience4j 라이브러리를 사용하여 서킷 브레이커를 적용하는 방법에 대해 알아보자.

왜 굳이 Resilience4j?

Resilience4j는 함수형 프로그래밍으로 설계된 경량 장애 허용 라이브러리로, 서킷 브레이커 패턴을 위해 사용된다. 서킷 브레이커 라이브러리에는 Hystrix도 있는데, Hystrix와 달리 Resilience4j는 다른 라이브러리의 의존성이 없어 가볍기 때문에 Resilience4j를 사용하였다. 참고로 Resilience4j는 CircuitBreaker이외에도 TimeLimiter, Retry등과 같은 다양한 장애 대응 패턴을 제공한다.

  • CircuitBreaker : CircuitBreaker 패턴을 구현하며, 서비스 호출의 장애를 모니터링하고 지정된 임계값 이상의 실패가 발생하면 서비스 호출을 차단하여 더 많은 장애를 방지한다.
  • TimeLimiter : 서비스 호출의 최대 실행 시간을 제한하여 서비스 호출이 지나치게 길게 실행되는 것을 방지한다.
  • Retry : 재시도 메커니즘을 제공하여 서비스 호출 중 장애가 발생했을 때 자동으로 재시도하고, 지수 백오프 등의 전략을 사용하여 재시도 간격을 조절할 수 있다.

서킷 브레이커 적용

1. 의존성 추가

	  implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
	  implementation 'org.springframework.boot:spring-boot-starter-actuator'
	  implementation 'org.springframework.boot:spring-boot-starter-aop'
	  implementation 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0'
//	  implementation 'io.github.resilience4j:resilience4j-timelimiter:2.2.0'
//	  implementation 'io.github.resilience4j:resilience4j-retry:2.2.0'

timelimiter와 retry도 필요하다면 추가해서 사용하면 된다.


2. application.yml 설정

resilience4j:
  circuitbreaker:
    configs:
      default:
        registerHealthIndicator: true
        slidingWindowType: TIME_BASED
        slidingWindowSize: 10
        minimumNumberOfCalls: 10
        slowCallRateThreshold: 100
        slowCallDurationThreshold: 60000
        failureRateThreshold: 50
        permittedNumberOfCallsInHalfOpenState: 10
        waitDurationInOpenState: 10s
    instances:
      searchProductCircuitBreaker:
        baseConfig: default
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include:
          - "*"
  health:
    circuitbreakers:
      enabled: true
  • slidingWindowType: sliding window 타입을 결정한다. COUNT_BASED인 경우 slidingWindowSize만큼의 마지막 call들이 기록되고 집계된다. TIME_BASED인 경우 마지막 slidingWindowSize초 동안의 call들이 기록되고 집계된다.
  • slidingWindowSize: CLOSED 상태에서 집계되는 슬라이딩 윈도우 크기를 설정한다. (기본값: 100)
  • minimumNumberOfCalls: minimumNumberOfCalls 이상의 요청이 있을 때부터 failure/slowCall rate를 계산한다. 예를들어, 해당값이 10이라면 최소한 호출을 10번을 기록해야 실패 비율을 계산할 수 있다. (기본값: 100)
  • slowCallRateThreshold: 임계값을 백분율로 설정, 서킷 브레이커는 호출에 걸리는 시간이 slowCallDurationThreshold보다 길면 느린 호출로 간주, 해당 값을 넘어갈 시 서킷 브레이커는 OPEN 상태로 전환되며, 이때부터 호출을 차단한다. (기본값: 100)
  • slowCallDurationThreshold: 호출에 소요되는 시간이 설정한 임계치보다 길면 느린 호출로 계산한다. (기본값: 60000[ms])
  • failureRateThreshold: 실패 비율 임계치를 백분율로 설정. 해당 값을 넘어갈 시 서킷 브레이커는 OPEN 상태로 전환되며, 이때부터 호출을 차단한다. (기본값: 50)
  • permittedNumberOfCallsInHalfOpenState: HALF_OPEN 상태일 때, OPEN / CLOSE 여부를 판단하기 위해 허용할 호출 횟수를 설정한다. (기본값: 10)
  • waitDurationInOpenState: OPEN 상태에서 HALF_OPEN 상태로 전환하기 전 기다리는 시간 (기본값: 60000[ms])

3. 서킷 브레이커 적용

 @CircuitBreaker(name = "searchProductCircuitBreaker", fallbackMethod = "fallback")
    public String searchProduct(String query, int display) {
        String text = URLEncoder.encode(query, StandardCharsets.UTF_8);
        String apiURL = "https://openapi.naver.com/v1/search/shop.json?query=" + text + "&display=" + display;

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(apiURL))
                .header("X-Naver-Client-Id", clientId)
                .header("X-Naver-Client-Secret", clientSecret)
                .GET()
                .build();
                
         ...
    }
    
    public String fallback(String query, int display, Throwable t) {
        return "Fallback! exception type: " + t.getClass() + ", message: " + t.getMessage();
    }
  • 외부 API를 호출하는 메서드 위에 @CircuitBreaker 어노테이션을 작성하여 application.yml에서 선언한 "searchProductCircuitBreaker" 인스턴스 명을 삽입한다.
  • 서킷이 open되면 실행할 메서드를 fallbackMethod로 선언한다.
  • fallbackMethod는 해당하는 메서드 파리미터(String query, int display)도 fallbackMethod 파라미터로 같이 지정해줘야 한다.

4. 결과

요청 실패 비율이 임계치를 넘어가면 다음과 같이 에러 메시지가 뜨면서 서킷 브레이커가 OPEN 상태가 된다.


{domain}/actuator/health 엔드포인트를 통해 서킷 브레이커의 상태를 확인할 수 있다.


{domain}/actuator/circuitbreakerevents에서는 각각의 요청에 대한 상세한 로그를 볼 수 있다.


❗️resilience4j의 버전으로 인한 에러

서킷 브레이커를 적용할 때 application.yml 파일에서 show-details: always 로 설정했음에도 불구하고 서킷 브레이커의 세부사항들이 보이지 않는 문제가 발생했었다. 알고 보니 resilience4j의 버전 문제였다. 만약 같은 오류가 발생했다면 공식 문서에서 안정화된 최신 버전을 사용해보길 바란다.


🔗 참고 자료


👉 관련 포스팅

1. 서킷 브레이커(CircuitBreaker)의 필요성과 동작 원리

profile
솨의 개발일기
post-custom-banner

0개의 댓글