003 Circuit Breaker

Spring cloud circuit breaker #

Circuit breaker #

  • 전기 회로의 차단기와 유사한 역할을 하는 소프트웨어 디자인 패턴
  • 특정 서비스가 과부하 상태가 되었을 때 추가적인 요청을 차단하여 시스템의 안정성을 유지
  • 해당 서비스가 복구될 수 있는 시간 확보
  • Reactive systems의 Resilient(복원력)를 지원

Spring Cloud Circuit Breaker #

  • Spring에서 circuit breaker를 지원하기 위해 만든 라이브러리
  • resilience4j와 spring retry를 추상화하여 Circuit breaker를 지원
    • reactive 환경에서는 resilience4j 만 사용 가능 img.png

Resilience4j #

  • java에서 circuit breaker를 지원하는 라이브러리
  • Circuit breaker, Rate Limiter, Retry, Bulkhead, Time Limiter, Cache 등의 기능을 제공 img_1.png

Circuit breaker 준비 #

  • org.springframework.cloud:springcloud-starter-circuitbreaker-reactorresilience4j 를 추가
  • mavenBom에 cloud-dependencies 버전을 명시
  • circuit breaker에는 별도의 버전 명시 없이 사용 가능 img_2.png

Circuit breaker 상태 #

  • Circuit breaker의 상태는 finite state machine으로 표현 가능
  • Closed, Open, Half Open으로 구성
    • Closed: 정상적으로 요청을 받을 수 있는 상태
    • Open: Circuit breaker가 작동하여, 목적지로 가는 트래픽, 요청을 막고 fallback 반환
    • Half Open: 트래픽을 조금 흘려보고 Open을 유지할지 Closed로 변경할지 결정 img_3.png

Circuit breaker 상태 변화 #

  • Closed에서 Open으로 변경
    • 호출하는 대상에 문제가 생긴 경우
  • Open에서 Half Open으로 변경
    • 일정 시간이 지나거나
    • 강제로 상태 변경
  • Half Open에서 Open 혹은 Close로 변경
    • half open 동안 실제 대상을 호출하여 문제가 없는지 검증 img_4.png

Reactor transform 연산자 #

  • 원본 publisher를 인자로 전달 받고 변환된 publisher를 반환
  • 통째로 넘기기 때문에 다른 연산자보다 더 유연하게 데이터 스트림을 처리 가능 img_5.png

Reactor transform 연산자 테스트 #

  • 함수형 인터페이스를 직접 구현
  • apply에서 인자로 현재 mono를 받고 각각의 item에 1을 더하는 연산자를 추가하여 반환 img_6.png
  • error를 반환하는 Flux를 무조건 1을 반환하는 Flux로 변환하여 반환 img_7.png

Circuit breaker closed #

  • 기본 상태
  • 들어오는 모든 요청을 대상 메소드, 서비스에 전달
  • 서비스에 전달 후 응답이 느리거나 error가 발생한다면 fallback을 실행하여 결과 전달 img_8.png

closed 테스트 서비스 #

  • ReactiveCircuitBreakerFactory bean 주입
  • doGreeting을 호출하여 특정 delay 이후 인사를 반환하는 Mono 획득
  • transform을 사용하여 해당 publisher를 circuit breaker에 전달
  • “normal” id를 갖는 circuit breaker를 생성
  • fallback으로 fallbackMessage를 반환 img_9.png img_10.png

closed 테스트 어노테이션 #

  • GreetingCircuitBreakerServiceTest를 생성
  • AutoConfigureReactiveCircuitBreaker를 만들어서 제공 img_11.png

closed 테스트 설정 #

  • 로깅을 위한 customizer 추가
  • 요청이 성공하거나 문제가 발생하거나
  • circuit breaker의 상태가 바뀌거나
  • slowCallRate, failureRate 등이 바뀌면 로깅 img_12.png
  • eventLogger 외에는 모두 기본 설정
  • configureDefault를 통해 별도의 설정을 갖지 않는 다른 모든 circuit breaker에 해당 설정이 적용
    • “normal”에 대한 별도의 설정을 제공하지 않기 때문에 normal circuit breaker는 default config를 사용 img_13.png

closed 테스트 #

  • 테스트 생성
  • Greeter를 @SpyBean으로 설정
  • GreetingCircuitBreakerService와 CircuitBreakerRegistry를 @Autowired로 successMessage과 fallbackMessage를 지정 img_14.png

success 예제

  • delay를 0ms로 제공
  • 바로 기대한 결과인 “Hello grizz!” 를 반환
  • circuitBreaker id를 normal로 설정했기 때문에 logging에서도 normal로 남는다
  • greeter의 generate는 한번 실행 img_15.png img_16.png

GreetingCircuitBreakerServiceTest img_17.png

fallback 예제

  • delay를 5000ms로 제공
  • withVirtualTime을 사용해서 시간이 지난 것처럼 시뮬레이션
  • 1초가 지나는 시점에 “Hello world!” 반환
  • 5초를 기다리게 해도 결과는 동일
  • circuit breaker에서 TimeoutException 발생시키고 fallback 실행
  • greeter는 중간에 cancel되어 실행되지 않음 img_18.png

exception이 발생한 경우도 fallback 실행

  • it 이 Mono의 결과 (error) img_19.png

Circuit breaker sliding window #

  • circuit breaker는 대상이 되는 서비스 호출과 관련된 성공, 실패 여부를 sliding window 형태로 저장
  • count-based sliding window에서는 특정 개수 n개만큼의 측정 결과를 저장
  • 저장된 측정 결과의 개수가 n개에 도달하면 최초에 저장했던 결과를 제거하고 새로운 결과를 저장
    • circular array를 사용
  • 각각의 과정에서 전체 개수 대비 실패 비율을 계산
    • 이를 failure rate 라고 부른다 img_20.png

sliding window와 failure rate #

  • failure rate = 실패한 호출 수 / sliding window 크기
  • item 하나가 새로 추가되면 failure rate 계산에 O(1) 만큼의 시간복잡도가 필요
    • sliding window 크기는 고정이기 때문에 총 합에서 새로운 값을 더하고 없어진 값을 제거하여 다시 계산
  • failure rate이 설정한 임계치에 도달하는 순간 closed 상태에서 open 상태로 변경 img_21.png

closed에서 open으로 전환 #

  • closed 상태에서 open 상태로 바뀌면서 circuit breaker는 state transition 이벤트를 발행 img_22.png

closed 전환 테스트 #

  • circuit breaker id를 외부에서 바꿀 수 있게 추가 img_23.png

  • sliding window size를 4

    • 기본값은 100
  • failure rate threshold를 50

    • failure rate 임계치를 퍼센티지로 표현
    • 기본값은 50이고 0에서 100 사이
  • “mini” 라는 이름을 갖는 circuit breaker 생성

  • sliding window size가 4이고 failure rate threshold가 50이기 때문에 전체 호출 중 절반이 실패하는 순간 open으로 전환 img_24.png

  • 처음 4개가 성공하여 failureRate이 0.0

  • 이후 2개가 실패하고 failureRate이 50.0이 되면서 open 상태로 전환 img_25.png img_26.png

Circuit breaker open #

  • open 상태에서는 기존에 호출하던 대상을 더이상 호출하지 않는다
  • fallback이 실행되어 반환
  • 호출하는 서비스를 보호하고 복구할 수 있는 시간 확보
  • fallback이 실행되기 때문에 서비스의 장애가 전파되지 않는다 img_27.png

open 테스트 #

  • open 상태에서는 delay를 0으로 전달해도 무조건 fallback 메세지 반환
    • closed 상태였다면 무조건 성공
  • 100번을 호출했지만, spyGreeter는 처음 성공했던 4번 호출 이후 한번도 호출되지 않는다 img_28.png

open에서 half open으로 #

  • open 상태에서 half open 상태로 바뀌면서 circuit breaker는 state transition 이벤트를 발행
  • open 상태에서 half open으로 가기 위해서는 2가지 방법이 존재
    • circuit breaker api를 사용해서 명시적으로 변경
    • 옵션을 지정하여 특정 시간이 지나면 자동으로 변경 img_29.png

open 전환 테스트 #

  • 처음 4번 실패하게 만들어 open 상태로 전환
  • CircuitBreakerRegistry를 이용해서 circuitBreaker에 접근하고 transitionToHalfOpenState를 호출
  • 하지만 직접 상태를 관리해야 하기 때문에 어쩔 수 없는 겨우가 아니라면 가급적 권장 하지 않는다 img_30.png img_31.png

open 전환 테스트 #

  • enableAutomaticTransitionFromOpenToHalfOpen: 일정 시간이 지나면 자동으로 open에서 half open으로 변경되게 설정
  • waitDurationInOpenState: 얼마만큼의 시간이 지난 후 half open으로 변경할지 설정 img_32.png

예제

  • 처음 4개를 실패 상태로 만들어 open 상태로 waitDurationInOpenState를 5초로 설정했기 때문에 6초동안 대기
  • 이후 자동으로 half open 상태로 변경 img_33.png img_34.png

Circuit breaker half open #

  • half open 상태에서 정해진 개수만큼 요청을 전달
  • half open에서 측정한 결과에 따라서 open 혹은 closed로 변경
  • 대상 서비스가 복구된 경우, 자동으로 장애 복구하기 위한 목적 img_35.png

half open에서 closed 혹은 open #

  • half open 상태에서 sliding window와 같이 특정 개수(permittedNumberOfCalls)에 대해서 측정 결과 저장
  • half open의 failure rate = 실패한 호출 수 / permittedNumberOfCalls
  • open으로 전환 : failure rate이 임계점보다 높거나 같다면
  • close로 전환 : failure rate이 임계점보다 낮다면 img_36.png

half open 전환 테스트 #

  • 3초가 지나면 자동으로 open에서 half open으로 전환
  • half open 상태에서 6개의 요청을 분석하고 3개 이상 실패하면 open으로 2개 이하 실패하면 closed로
    • permittedNumberOfCallsInHalfOpenState가 6이라서 6개의 요청을 분석
    • failureRateThreshold가 50이기 때문에 3개가 기준이 된다 img_37.png

close로 변경 예제

  • circuit breaker를 half open 상태로 변경
  • 4번은 성공, 2번은 실패
  • 6번 (permittedNumberOfCalls) 요청이 끝나는 시점에 failureRate은 33퍼센트
  • 50퍼센트 (failureRateThreshold) 보다 작기 때문에 closed로 상태 변경 img_38.png

open으로 변경 예제

  • circuit breaker를 half open 상태로 변경
  • 3번은 성공, 3번은 실패
  • 6번 (permittedNumberOfCalls) 요청이 끝나는 시점에 failureRate은 50퍼센트
  • 50퍼센트 (failureRateThreshold) 보다 크거나 같기 때문에 open으로 상태 변경 img_39.png

Circuit breaker 설정 #

  • ReactiveResilience4JCircuitBreakerFactory에 CircuitBreakerConfig와 TimeLimiterConfig 제공
  • CircuitBreakerConfig는 circuit breaker 설정 제공
  • TimeLimiterConfig는 timeout과 관련된 설정 제공 img_40.png

CircuitBreakerConfig 설정 #

  • slidingWindowSize: 호출 결과를 저장할 sliding window 크기. 기본 100
  • failureRateThreshold: 실패 비율 임계점. 기본 50
  • enableAutomaticTransitionFromOpenToHalfOpen: open에서 half open으로 자동 전환할지 여부. 기본 false
  • waitDurationInOpenState: open에서 half open로 전환될 때까지 필요한 시간. 기본 60초
  • permittedNumberOfCallsInHalfOpenState: half open 상태에서 허용할 호출 수. 기본 10
  • ignoreExceptions: 서비스에서 exception을 던지는 경우, 허용할 exceptions 목록. 기본 empty
  • maxWaitDurationInHalfOpenState: half open 상태에서 대기할 수 있는 최대 시간. 기본 0 img_41.png

TimeLimiterConfig 설정 #

  • cancelRunningFuture: future가 진행중인 경우, cancel 할지 여부. 기본 true
  • timeoutDuration: timeout 기준 시간. 기본 1초
  • factory.configure에 circuit breaker id를 전달하여 특정 id를 갖는 circuit breaker들에 대한 설정 가능
  • 일반적인 모든 circuit breaker에 설정을 적용하려면 configureDefault를 사용 img_42.png

Circuit breaker yaml 설정 #

  • yaml을 통해서도 설정 가능
  • resilience4j.circuitbreaker.instances.로 특정 circuit breaker instance의 설정을 주입
  • resilience4j.circuitbreaker.instances.example을 앞선 @Bean 설정과 동일 img_43.png
  • resilience4j.circuitbreaker.configs.default를 설정하여 정확히 일치하는 instance가 없는 circuit breaker에 대한 설정 제공 img_44.png

Circuit breaker group #

  • circuit breaker instance를 만들면서 group을 제공할 수 있다
  • 아래의 순서로 설정을 찾게 된다
    • instance id와 정확히 일치하는 설정 사용
    • 없다면, group과 정확히 일치하는 설정 사용
    • 없다면, default 설정 img_45.png img_46.png

  1. 강의 : Spring Webflux 완전 정복 : 코루틴부터 리액티브 MSA 프로젝트까지_