[Resilience4j] Circuit Breaker With Spring Boot 2

2022. 7. 10. 22:08개발/Spring

Resilience4j의 Circuit Breaker에 대한 개념을 정리하고 이를 Spring 프로젝트에 적용해본다.

1.기본 개념

1.1 Circuit breaker State

서킷 브레이커의 3가지 상태

  • 서킷 브레이커는 CLOSED, OPEN, HALF_OPEN 3가지의 상태를 가진다
    • CLOSED는 요청 집계값이 threshold 미만인 상태를 유지하며 정상적으로 요청이 가능한 상태.
    • OPEN은 요청 집계값이 threshold를 초과해서 서비스간 요청이 불가능한 상태.
    • HALF_OPENOPEN 상태에서 특정 대기시간이 경과하면 발생하는 상태이며, 추가적인 요청 집계값이 threshold 보다 낮으면 CLOSED로 전환되고, 반대의 경우 다시 OPEN으로 복귀된다.
  • 여기에 2개의 특수한 상태인 DISABLED, FORCED_OPEN이 있다.
  • 서킷 브레이커는 Sliding window를 통해 요청에 대한 측정값을 집계한다.
  • count-based 방식과 time-based 방식을 제공한다.
  • count-based 방식은 가장 최근 N번의 요청 횟수를 집계해서 상태변경에 참고한다.
  • time-based 방식은 가장 최근 N초의 요청 횟수를 집계해서 상태변경에 참고한다

1.2 Count-based sliding window

  • Count-based sliding window는 N사이즈의 원형 배열로 구현된다.
  • sliding window의 사이즈가 10이라면 원형 배열은 항상 10개의 측정값을 가지고 있다.
  • 새로운 요청을 수행할때마다 집합을 수정한다.
  • 이미 10개의 측정값을 가진 상태에서 새로운 값이 들어오면 가장 오래된 측정값이 퇴출된다.

1.3 Time-based sliding window

  • Time-based sliding window는 N개의 부분 원형 배열로 이뤄진다.
  • sliding window의 사이즈가 10이라면 10개의 부분 원형 배열을 가지게 된다.
  • 부분 원형 배열을 bucket이라고 한다.
  • 각 bucket은 특정 epoch 초 내에 발생한 모든 요청들을 집계한다.
  • 헤드 bucket은 현재 epoch초를 저장하고 있다. (= 가장 최신정보를 저장한다.)
  • 그외 bucket은 헤드 bucket 이전의 시간에서 발생한 호출 정보를 저장한다.
  • bucket이 10개일때 새로운 bucket이 들어온다면 가장 오래된 bucket이 퇴출된다.
  • bucket은 실패 요청 수 , 느린(slow) 요청 수 , 전체 요청 수 를 기록하고 있다.
  • 또한 모든 요청에 대한 전체 시간을 기록하고 있다.

1.4 Failure rate and slow call rate thresholds

1.4.1 Failure rate

  • 실패 비율이 설정한 threshold와 같거나 크면 서킷 브레이커의 상태는 CLOSED에서 OPEN 상태가 된다. (예. 집계된 요청중 50%가 실패하는 경우)
  • 모든 Exception은 실패로 간주된다.
  • 개발자는 어떤 Exception을 실패로 간주할지 설정할 수 있다.

1.4.2 Slow call rate

  • 느린 호출 비율이 설정한 threshold보다 크거나, 같으면 서킷 브레이커의 상태는 CLOSED에서 OPEN으로 변경된다. ( 예. 집계된 요청들 중에 50%가 요청을 완료하는데 5초 이상 걸리는 경우 )
  • 응답속도가 늦은 외부 시스템에 대한 로딩을 줄일 수 있다. (자체적인 대기 시간을 설정하기 때문.)

1.4.3 최소 측정 수

  • 최소 측정 수를 지정하면 해당 숫자만큼 호출을 받기전까진 서킷 브레이커를 검사하지 않는다.
  • 최소 측정 수를 10으로 했다면 9개의 실패 요청이 들어오더라도 그때까지 서킷 브레이커는 아무 작업도 하지 않는다.
  • 서킷 브레이커는 현재 상태가 OPEN 이면 CallNotPermittedException을 사용해서 추가적인 call을 거부한다
  • OPEN 상태에서 특정 대기 시간이 경과하면 서킷 브레이커 상태가 OPEN에서 HALF_OPEN으로 변경된다. 이후에 상태 검사를 위한 몇번의 호출이 허용된다.
  • 허용된 요청을 통해 상태 검사를 완료하기 전까진 추가적인 요청은 CallNotPermittedException 예외를 발생시키며 차단된다.
  • 실패 비율, 느린 요청 비율이 threshold보다 같거나 크면 상태는 다시 OPEN으로 변경된다.
  • 그 반대의 경우 CLOSED 상태로 변경된다.
  • 서킷 브레이커는 2개의 특수한 상태를 더 제공한다.
  • DISABLED는 모든 접근을 허용한다.
  • FORCED_OPEN은 모든 접근을 차단한다.
  • 이 2개의 상태를 벗어나는 방법은 상태 전환 트리거를 사용하거나, 서킷 브레이커를 리셋하는 방법뿐이다. ( 앞서 봤던 상태와 달리 특정 로직에 의해 살아나는 상태가 아님! )

1.5 configure a CircuitBreaker

1.5.1 slidingWindowType

  • 기본값은 COUNT_BASED이다.
  • COUNT_BASED,TIME_BASED를 사용하면 경우 slidingWindowSize 만큼의 요청을 집계하며, threshold를 만족하는지 검사한다.

1.5.2 slidingWindowSize

  • 기본값은 100이다.
  • 서킷 브레이커가 CLOSED 상태일때, 집계할 요청의 개수를 뜻한다.

1.5.3 slowCallDurationThreshold

  • 기본값은 60000 [ms]이다. (60초)
  • 느린 요청을 나누는 기준이 되는 값이다.

1.5.4 minimumNumberOfCalls

  • 기본값은 100이다.
  • 서킷 브레이커 상태 검사를 위한 최소한의 요청 개수를 뜻한다.

1.5.5 failureRateThreshold

  • 기본값은 50이다.
  • 실패 비율을 나타낸다. 만약 집계한 요청 100개 중 50개 이상이 실패라면 (50%이상) 서킷브레이커를 OPEN상태로 바꾼다.

1.5.6 automaticTransitionFromOpenToHalfOpenEnabled

  • 기본값은 false이다.
  • true로 세팅하면, 서킷브레이커가 자동으로 OPEN 상태에서 HALF_OPEN 상태로 변경되며 이 과정에서 트리거를 위한 어떤 요청도 필요하지 않는다.

1.5.7 eventConsumerBufferSize

  • 서킷브레이커나 벌크헤드 등 Resilience4j에서 제공하는 기능들은 자신이 발생시킨 이벤트를 기록하기 위한 버퍼를 가질 수 있으며, 그 버퍼 사이즈를 eventConsumerBufferSize로 설정가능하다.
  • 서킷 브레이커에서 발생할 수 있는 이벤트로는 성공, 실패, 리셋, 상태변경, 실패무시가 있다.

2. 시나리오 정의

  • 고객은 은행에서 계좌 생성을 하려고한다.
  • 고객은 화면을 통해 계좌생성 API를 요청한다.
  • 계좌생성 API는 계좌를 생성하기 전에 고객이 실제로 존재하는지 판단하기 위해 고객정보조회 API를 수행한다.
  • 계좌생성 API는 Account Service이며, 고객정보조회 API는 Customer Service이다.
  • Account Service에서 Customer Service로 API 호출을 수행해야하며, Customer Service에서 장애 발생시 Account Service에서 적절한 fallback 처리를 해줘야한다.

3. Spring에 resilience4j적용하기

3.1 Customer Service 개발

  • Customer Service에서 고객정보 조회를 위한 API를 작성한다.
  • 서킷 브레이커 테스트를 위해서 2초 동안 Sleep하도록 했다.

3.2 Account Service 개발

3.2.1 의존성 추가

  • Spring Boot 2에 대응하는 resilience4j 의존성을 추가한다.
  • 아직은 Spring 프로젝트에서 관리하지 않기 때문에 이름이 github으로 되어 있는것을 볼 수 있다.

3.2.2 서킷 브레이커 적용

  • @CircuitBreaker 어노테이션을 통해 메소드에 서킷 브레이커를 적용할 수 있다.
  • name에 backendA가 있는데 서킷 브레이커 인스턴스의 이름이다. 인스턴스에 대한 정보는 뒤에 application.yml에서 작성한다.
  • fallbackMethod는 서킷 브레이커의 상태가 OPEN일때 요청을 수행하면 작동하는 메소드를 정의한다.

  • 테스트를 위해서 고객정보 조회 API를 20회 수행하도록 한다.

3.2.3 Count Based 서킷 브레이커 테스트

  • application.yml에 서킷 브레이커 인스턴스에 대한 설정을 할 수 있다.
  • 이름이 backendA인 인스턴스를 생성한다.
  • slidingWindowType이 COUNT_BASED이므로 최근 요청에 대한 개수를 집계한다.
  • 느린 요청 비율이 70% 이상이거나 실패한 요청의 비율이 70% 이상이면 상태가 OPEN으로 변경되도록 한다.
  • minimumNumberOfCalls를 통해 상태 변경 검사를 위해서 최소 10개의 요청이 집계 되도록한다.
  • slowCallDurationThreshold를 통해서 느린 요청의 기준치를 작성해준다. 요청을 완료하는데 1초 이상 소요되면 느린 요청으로 간주된다.
  • 앞서 설명 했듯이 요청에 대해 Exception이 발생하면 실패로 간주한다.

  • 수행 결과를 확인한다.
  • 10번째 요청을 완료한 순간 실패 계산을 수행하는데, 모든 요청이 느린 요청이기 때문에 100%의 실패율을 가진다. 따라서 10번째 요청을 완료한 순간에 서킷 브레이커는 OPEN 상태가 되어 11번째 부터는 요청이 불가능하다.
  • 서킷 브레이커가 OPEN 상태일때 요청을 수행하면 CallNotPermittedException 예외를 발생시킨다.

3.2.4 느린 요청 관련 설정 제거하기

  • 느린 요청 관련 설정을 제거하면 실패 비율로만 서킷 브레이커의 상태를 제어하게 된다.
  • Exception인 경우만 실패로 간주하는데 현재 코드는 속도는 느리지만 Exception을 발생시키진 않는다. 이때 서킷 브레이커가 어떻게 작동하는지 살펴보자.

느린 요청 관련 설정 제거

  • 예상대로 서킷 브레이커가 CLOSED 상태이므로 모든 요청에 대해서 정상적으로 수행된것을 확인할 수 있다.
  • 가능하면 실패 횟수 비율과 느린 호출 비율에 대한 Threshold를 함께 사용함으로써 사용자가 장시간 대기하는 일은 없도록 하자 ( 예. 1분이 지나도록 요청에 대한 결과가 없다면 그냥 장애로 판단. )

3.2.5 minimumNumberOfCalls 값을 slidingWindowSize보다 크게 설정하기

  • 아래와 같이 minimumNumberOfCalls를 크게 변경했다.
  • minimumNumberOfCalls가 100이기 때문에 100번의 요청이 들어오기 전까지는 검사를 수행하지 않을것으로 예상된다.
  • 하지만 요청 횟수가 Sliding window size에 도달하는 순간 서킷 브레이커 상태 검사를 수행한다.
  • 여기서 알 수 있듯이 minimumNumberOfCalls의 값에 도달하기 전에 slidingWindowSize의 값을 만족한다면 minimumNumberOfCalls에 상관없이 먼저 상태검사를 수행한다.
  • 가능하면 minimumNumberOfCallsslidingWindowSize보다 같거나 작게 작성해서 의미를 가질 수 있도록 한다. (default값은 둘다 100으로 같다.)

3.2.6 Time Based 서킷 브레이커 적용하기

  • slidingWindowType을 TIME_BASED로 변경한다.
  • slidingWindowType은 시간을 의미하게 된다.
  • 10초 내에 발생한 요청 중 실패 요청 비율이 70%를 넘거나, 느린 요청의 비율이 70%를 넘어가면 서킷은 OPEN 상태가 된다.

  • 10초 내에 5번의 요청이 들어왔기 때문에 검사를 시작한다.

  • 사용자 조회 API를 수행하는데 최소 2초가 걸리기 때문에 minimumNumberOfCalls를 6으로 수정하면 서킷 브레이커의 상태 검사가 영원히 발생하지 않는다.
  • 상태 검사를 위해선 10초 내에 발생한 요청이 최소 6번은 되어야 하는데 현재 로직상 10초내에는 아무리 많이 요청이 발생해도 5번이 최대치다.

  • minimumNumberOfCalls를 6으로 수정했을 뿐인데 서킷 브레이커가 CLOSED로 유지된다.
  • TIME_BASED 방식을 사용하기 위해선 꽤 정교한 설정값 세팅이 필요하다. 이런 이유 때문인지 slidingWindowType의 기본값은 COUNT_BASED이다.

4. 참고자료


 

CircuitBreaker

Getting started with resilience4j-circuitbreaker

resilience4j.readme.io

 

Implementing a Circuit Breaker with Resilience4j

A deep dive into the Resilience4j circuit breaker module. This article shows why, when and how to use it to build resilient applications.

reflectoring.io

 

Difference between sliding window size and minimum number of calls

I am new to circuit breakers and have recently implemented them in one of my services. I was going through the documentation Resilience 4J official documentation and found two properties that we can

stackoverflow.com