IT/Spring

resilience4j 기본 개념

물통꿀꿀이 2020. 12. 22. 12:21

resilience4j의 기본 개념이나 코어 모듈에 대해 간략히 알아보도록 하겠다.

 

Retry

해당 모듈은 말 그대로 "재시도" 관련 모듈이다.

재시도 횟수 등등을 설정을 할 수 있는데 아래 정리된 표를 보면 보다 이해하기 쉽니다.

Config Property Default value Description
maxAttempts 3 최대 재시도 횟수
waitDuration 500 [ms] 재시도 간격
즉, 다음 재시도 전 대기 시간
intervalFunction numOfAttempts -> waitDuration 실패 이후 대기 시간에 수행하는 작업을 대체하는 함수 
즉, 기본은 waitDuration 만큼 대기하지만 무조건 대기하지 않고 다른 작업을 추가
retryOnResultPredicate result -> false 결과 값에 대해 재시도를 해야하는지 판단
(재시도가 필요하면 true)
retryOnExceptionPredicate throwable -> true 예외에 대해 재시도를 해야하는지 판단
(재시도가 필요하면 true)
retryExceptions empty 실패로 처리하고 재시도 해야하는 Throwable 클래스 리스트 (하위 클래스 호환)
ignoreExceptions empty 무시해야 (재시도 X) 하는 Throwable 클래스 리스트 (하위 클래스 호환)

TimeLimiter

해당 모듈은 "타임아웃" 관련 모듈이다.

Config Property Default value Description
cancelRunningFuture true 타임아웃이후 Future를 취소할지 결정
(취소는 true)
timeoutDuration 1000 [ms] 타임아웃 시간

Bulkhead

해당 모듈은 "동시 실행" 관련 모듈이다. (Concurrent execution 관련)

Config Property Default value Description
maxConcurrentCalls 25 동시에 실행할 수 있는 최대 수
maxWaitDuration 0 스레드가 실행할 수 있는 최대 시간 (Blocking)

ThreadPoolBulkhead

해당 모듈은 "스레드풀" 관련 모듈이다.

Config Property Default value Description
maxThreadPoolSize Runtime.getRuntime()
.availableProcessors()
최대 스레드 풀 사이즈
coreThreadPoolSize Runtime.getRuntime()
.availableProcessors() - 1
Core 스레드 풀 사이즈
queueCapacity 100 Queue의 capacity
keepAliveDuration 20 [ms] 스레드의 수가 core 스레드 수보다 클 경우,
idle 스레드가 종료되기 전에 새로운 작업을 기다리는 최대 시간

RateLimiter

해당 모듈은 reliable 서비스를 제공하기 위해 필수적이다. (아래 원문 참조)

Rate limiting is an imperative technique to prepare your API for scale and establish high availability and reliability of your service. But also, this technique comes with a whole bunch of different options of how to handle a detected limits surplus, or what type of requests you want to limit. You can simply decline this over limit request, or build a queue to execute them later or combine these two approaches in some way.

RateLimiter는 내용이 조금 복잡할 수 있기 때문에, 아래 글을 먼저 읽고 오는 것을 추천한다.

dzone.com/articles/rate-limiter-internals-in-resilience4j

 

Rate Limiter Internals in Resilience4j - DZone Performance

We take a look at the methods of dealing with rate limiting in the open source framework, Resilience4j, and how it can help boost your app's performance.

dzone.com

간단히 원문에서 제공하는 그림을 바탕으로 보면, 내부적으로 epoch 시작부터 모든 nanoseconds를 cycle로 나눈다.

그림에서 대략 파악할 수 있듯이, 권한의 획득(acquire)과 해제(release)의 연속이다.

각 cycle은 시작 시마다 권한을 가지고 있는데 스레드가 시작할 때, cycle의 권한을 가져온다. 때문에 순간적으로 다른 스레드는 권한을 얻을 수 없기 때문에 다음 cycle에서 권한을 가져오길 기다린다.

이처럼 RateLimiter는 속도를 제어하면서 호출이 한 번에 몰리는 것을 제한할 뿐만 아니라 동시처리 환경에서도 적절하게 동작할 수 있도록 한다.

 

Resilience4j는 RateLimier를 2가지 타입 (SemaphoreBasedRateLimiter, AtomicRateLimiter)를 제공하지만 기본 타입은 AtomicRateLimiter이다. AtomicRateLimiter의 필드 값은 아래와 같다.

- activeCycle: 마지막 호출에서 사용된 cycle 번호

- activePermissions: 마지막 호출후에 사용가능한 권한 수

- nanosToWait: 마지막 호출의 권한을 기다리는 nanoseconds

 

이 외에도 RateLimiter에 설정 값은 아래와 같다.

Config Property Default value Description
timeoutDuration 5 [s] 스레드가 권한을 기다리는 기본 시간
limitRefreshPeriod 500 [ns] limit refresh 기간으로, 각 기간 후에 rate limiter는 권한 개수를 limitForPeriod 값으로 설정
limitForPeriod 50 한 번의 limit refresh 기간 동안 사용 할 수 있는 권한 수

.해당 설정값은 조금 헷갈릴 수 있는데, 다시 돌이켜보면...

cycle이 시작되면 사용할 수 있는 권한 수가 있는데 해당 권한 수는 limitForPeriod로 설정이 가능하다. 또한 limitForPeriod의 초기화 즉, refresh가 다시 발생했을 때 다시금 limitForPeriod 값을 가질 수 있다. 만일, 스레드가 사용할 권한이 없다면 해당 스레드는 timeoutDuration 만큼 대기하여 재요청한다.

 

Cache

해당 모듈은 말 그대로 캐시 관련한 모듈이다.

 

CircuitBreaker

해당 모듈은 Circuit을 제어하는 모듈이다.

내부적으로 제어하는 방식은 2가지 인데 간단히 알아보면 다음과 같다.

1) Count-based sliding window

카운팅을 통해 제어하겠다는 방법으로 N개를 담을 수 있는 원형 배열 (circular array)로 구성된다.

예를 들어, 크기가 10이면 해당 배열은 10까지 담을 수 있고 그 이후엔 가장 오래된 것을 삭제하고 새로운 것을 넣는다.

(자료구조에서 보면 알 수 있듯이, N+1이 가장 처음이기 때문에 찾기 쉽다.)

 

2) Time-based sliding window

시간을 통해 제어하는 방법으로 window의 기준 값은 "초"이다.

내부 동작은 카운팅 동작처럼 오래된 것을 삭제하고 새로운 것을 넣는다. 그러나 조금 다른 부분은 N개의 부분 bucket을 가진다는 것이다.

특정 초에서 발생한 결과 값을 bucket에 저장하는데 가장 최근 값을 bucket head에 넣고 직전 값은 다른 bucket에 넣는 방식을 취한다.

 

이외에도 Failure rate and slow call rate thresholds 라는 부분이 있는데 간단히 살펴보자.

말 그대로 thresholds를 가지고 있기 때문에 특정 수치에 대해 핸들링을 할 수 있다. 예를 들어, 요청의 50% 이상이 실패했을 때 서킷을 CLOSED에서 OPEN으로 전환 할 수 있다. 또한 요청의 50% 이상이 5초 이상 걸리면 CLOSED에서 OPEN으로 전환할 수 있다.

이처럼 내부적 계산 및 rate를 따져서 서킷을 핸들링한다. (아래 그림 참조)

설정할 수 있는 값은 아래와 같다.

Config Property Default value Description
failureRateThreshold 50 실패 비율 threshold를 설장한다.
해당 값보다 같거나 클 경우 Circuit을 open
slowCallRateThreshold 100 해당 값보다 같거나 클 경우 느리다고 판단하여 Circuit을 open
slowCallDurationThreshold 60000 [ms] Configures the duration threshold above which calls are considered as slow and increase the rate of slow calls.
permittedNumberOfCalls
InHalfOpenState
10 서킷브레이커가 half-open 일때, 허용되는 요청 횟수
maxWaitDurationInHalfOpenState 0 서킷브레이커가 half-open에서 open 전환 전 대기하는 최대 시간
(값이 0이면 half-open 상태에서 받은 모든 요청이 완료될 때까지 무한 대기) 
slidingWindowType COUNT_BASED sliding window 타입 설정
slidingWindowSize 100 sliding window 크기 설정
minimumNumberOfCalls 100 open 상태일 때 받는 최소 호출 수로 close 상태로 전환하는 호출 기준
(즉, 해당 값이 10일 때 9번 호출이 들어왔고 모두 실패했다 하더라도 close로 전환하지 않음)
waitDurationInOpenState 60000 [ms] 서킷브레이커가 open에서 half-open으로 전환하기 전 대기 시간
automaticTransition
FromOpenToHalfOpenEnabled
false true일 경우 내부적으로 스레드가 모니터링하여 half-open 상태로 자동으로 전환
recordExceptions empty 실패로 간주 할 수 있는 예외 클래스 리스트.
ignoreExceptions empty 무시할 수 있는 예외 클래스 리스트
recordException throwable -> true

By default all exceptions are recored as failures.
예외가 발생 했을 때, 실패로 간주 할지 판단
ignoreException throwable -> false

By default no exception is ignored.
예외가 발생 했을 때, 실패로 간주 할지 판단