Reactor Resilience4J CircuitBreaker 알아보기
CircuitBreaker
- 필요한 이유 : Circuit Breaker 는 누전차단기로 전류를 차단하는 장치를 의미한다. 누전 차단기의 역할로 사고가 발생하기 이전에 전류를 차단하여 사고를 미리 방지하는것과 같이 문제가 있는 마이크로서비스간의 트래픽을 차단하여 해당 서비스로 인한 전체 장애를 방지한다.
- 최소요청 횟수 몇 회 이후 통계건수 기반으로 몇건을 평가했을때, 실패율이 얼마정도 되는지에 따라 CircuitBreaker가 Open 되고 이 Open 되는 시간의 지속시간을 얼만큼 유지할 것인지 등의 설정이 가능하다.
- 서킷브레이커 동작원리 : Consumer와 Producer 사이에 Circuit Breaker 을 두고 통신하며 Circuit Breaker는 아래의 그림과 같은 3가지 상태를 가진다.
- Close : 정상 상태
- Open : 일정 횟수 및 일정 시간 내에 정상 응답을 받지 못할 경우 Open 상태로 변경되면서 더이상 호출 및 대기를 하지 않고 Fallback method를 호출
- Half Open : Open 상태에서 일정 시간 이후 다시 마이크로 서비스를 호출해보는 상태를 말하며, 해당 응답 결과에 따라 Open, Close 상태를 결정한다
Resilience4j
Resilience4J : https://resilience4j.readme.io/docs/getting-started
Introduction
Resilience4j is a lightweight fault tolerance library designed for functional programming. Resilience4j provides higher-order functions (decorators) to enhance any functional interface, lambda expression or method reference with a Circuit Breaker, Rate Lim
resilience4j.readme.io
- 공식 가이드 문서를 보면 Resilience4J는 Hystrix에 영감을 받아 개발 되었으며, 쉬운 사용과 경량화를 지원하는 라이브러리로 소개 한다.
- Netflix 의 Hystrix 의 경우 더이상 업그레이드 작업은 없이 유지보수만 지원하겠다고 발표(2018.11) 하여 Resilience4J 사용을 권고 한다고 한다.
기본적인 동작 방식 : 2가지 (횟수 기반, 시간 기반)
- Count-based: The count-based sliding window aggregrates the outcome of the last N calls
- Time-based: The time-based sliding window aggregrates the outcome of the calls of the last N seconds.
- circuit breaker을 구성하기 위해 필요한 조건으로 {} 안의 값의 정의로 동작한다.
Failure rate and slow call rate thresholds
- 실패율, 슬로우 쿼리 계산은 call에 대한 결과를 sliding window에 저장하여 판단한다.
- 실패율은 최소 요청 횟수가 만족된 상태에서 계산할 수 있다. 예를 들어, 필요한 최소 요청 수가 10개인 경우 9개의 호출에 대해서 9개의 호출이 모두 실패하더라도 CircuitBreaker는 동작하지 않는다.
- OPEN 상태시 CallNotPermittedException 에러와 함께 호출이 거부된다. HALF_OPEN 상태로 변경 되었을 시 해당 백엔드를 다시 사용할 수 있을지 확인하기 위해 구성된 호출 수 만큼 다시 허용하며 구성된 임계값보다 크거나 같으면 OPEN으로 변경되고 임계값보다 낮다면 CLOSED로 변경된다.
- sliding window를 통해 요청 결과를 저장하거나 스냅샷을 읽을때는 동기적으로 수행된다. 오직 하나의 쓰레드만 특정 시점에 상태를 업데이트할 수 있다. 하지만 서킷 브레이커가 함수 호출을 동기화한다는 의미는 아니라고 하니 성능/병목 걱정은 하지 않아도 될듯하다
- CircuitBreaker는 thread-safe 하며 상태는 AtomicReference에 저장된다.
AtomicReference ?
AtomicReference 클래스는 멀티쓰레드 환경에서 동시성을 보장하며 자바에서 동시성 문제를 해결하는 방법 중 하나이다. "volatile" 은 Thread1에서 쓰고, Thread2에서 읽는 경우만 동시성을 보장한다. 두개의 쓰레드에서 쓴다면 문제가 될 수 있다. "synchronized"를 쓰면 안전하게 동시성을 보장할 수 있지만 비용이 크다. Atomic 클래스는 CAS 를 이용하여 동시성을 한다. 여러 쓰레드에서 데이터를 write해도 문제가 없다.
커스텀 CircuitBreaker 설정들
resilience4j:
circuitbreaker:
configs:
default:
slidingWindowSize: 10
failureRateThreshold: 80
# 실패에 대한 비율 퍼센트
# failureRateThreshold: 20
# 지연 응답에 대한 비율 퍼센트
# slowCallRateThreshold: 10
# 지연에 대한 판단 시간 (ms)
# slowCallDurationThreshold: 60000
# Half Open 상태일 때 허가된 요청 수
# permittedNumberOfCallsInHalfOpenState: 10
# Half-open 상태에서 대기할 수 있는 최대 시간으로 모든 허가된 요청이 완료될 때까지 0은 무한정 기다리는 것을 의미
# maxWaitDurationInHalfOpenState: 1000
# COUNT_BASED 또는 TIME_BASED 로 호출의 결과를 저장하고 집계하기 위한 슬라이딩 윈도의 타입
#slidingWindowType: COUNT_BASED
# 상태가 CLOSED 일 때 요청의 결과를 기록하기 위한 슬라이딩 윈도의 크기
# slidingWindowSize: 10
# 서킷이 실패율(failure rate) 또는 지연된 응답(slow call rate)을 계산하기 전 요구되는 최소 요청의 수
# minimumNumberOfCalls: 100
# 서킷이 OPEN 에서 Half-open 으로 변경되기 전 대기하는 시간
# waitDurationInOpenState: 10000
# actuator를 통해 circuitbraker 상태를 확인하기 위해 설정
registerHealthIndicator: true
# 실패/성공에 대한 집계를 하지 않음
ignoreExceptions:
- java.lang.RuntimeException
# 실패에 대한 집계를 하지 않는 exception
recordExceptions:
- org.springframework.web.client.HttpServerErrorException
- java.util.concurrent.TimeoutException
- java.io.IOException
operator:
slidingWindowSize: 10
failureRateThreshold: 80
registerHealthIndicator: true
instances:
operatorCircuitBreaker:
baseConfig: operator
# 사용하고자 하는 서킷브레이커의 이름
mollyTestCircuitBreaker:
# config 설정 값
baseConfig: default
polyTestCircuitBreaker:
baseConfig: default
failureRateThreshold: 60
value | default | 설명 |
---|---|---|
recordExceptions | empty | 실패로 기록될 예외의 리스트로 실패율(failure rate)가 증가되는 예외 리스트이다. 예외 리스트를 설정한다면, 다른 모든 예외는 ignoreExceptions에 의해 무시되지 않는다면 성공으로 간주된다. |
ignoreExceptions | empty | 성공 또는 실패로 기록되지 않는 예외들의 리스트이다. |
recordException | throwable -> true By default all exceptions are recored as failures. | 커스텀 Predicate로 예외가 실패로 기록될지 정의 할 수 있다. 예외가 실패로 카운트 되야 한다면 true를 리턴하고 성공으로 카운트 되야 한다면 false를 리턴해야 한다. (ignoreExceptions에 의해 무시되지 않는 경우) |
ignoreException | throwable -> false By default no exception is ignored. | 커스텀 Predicate로 예외가 무시될지 정의할 수 있다. 예외가 무시되려면 true를 리턴하고 실패로 카운트 되려면 false를 리턴해야 한다. - 상속받아 생성된 Exception도 무시된다. |
Custom CircuitBreaker 생성 코드 & 적용 테스트
https://resilience4j.readme.io/docs/examples-1
Examples
Examples of resilience4j-reactor
resilience4j.readme.io
@Bean
fun createCustomCircuitBreaker() : CircuitBreaker {
// 2개중 1개 실패
val circuitBreakerConfig = CircuitBreakerConfig.custom()
.slidingWindowSize(2)
.failureRateThreshold(10f)
.permittedNumberOfCallsInHalfOpenState(2)
.build()
return CircuitBreakerRegistry.of(circuitBreakerConfig)
.circuitBreaker("mytest")
}
테스트
- 블로그 api, 카페 api 요청을 기반으로 서킷 브레이커 테스트 작성