Resilience라는 단어의 사전적 의미는 회복성입니다. 심리학적 의학적인 영역에서는 어떤 시련이나 역경을 도약의 발판으로 삼아 더 높이 뛰어오르는 마음의 근력을 말하는데요. 시스템 관점에서의 Resilience의 의미도 크게 다르지 않은 것 같습니다. Resilience4j는 Netflix Hystrix(현재 deprecated)에서 영감을 받은 "fault tolerance" 라이브러리입니다. "fault tolerance"라는 키워드가 중요한 포인트인데 위키에 따르면 이렇게 나와 있습니다.
시스템을 구성하는 부품의 일부에서 결함(fault) 또는 고장(failure)이 발생하여도 정상적 혹은 부분적으로 기능을 수행할 수 있는 시스템이다.
어떤 시스템의 특정 일부분이 고장이 나거나 소위 장애가 발생하더라도 시스템 전체의 동작에는 영향을 주지 않는다는 의미로 해석할 수 있을 것 같습니다.
Resilience4j는 Circuit Breaker, Rate Limiter, Retry, Bulkhead, Time Limiter, Cache 등 여러 기능을 제공하는데요. 모든 기능을 다 사용할 필요는 없고 필요한 기능만 사용하면 됩니다. 하나하나 차근차근 알아보도록 하겠습니다.
Circuit Breaker
이름 그대로 회로 차단기의 역할을 하는 기능입니다. 회로에 과부하가 걸리면 이에 대한 피해를 막기 위해 차단기가 내려가게 되어 큰 피해를 막을 수 있습니다. 이와 유사하게 서버가 너무 많은 트래픽을 많아서 잠시 휴식을 취해야 한다면, 원래 기능을 잠시 멈추고 원래 기능을 대체할 수 있는 기능(fallback)을 임시적으로 수행할 수 있을 것입니다. 그렇게 되면 사용자 입장에서는 전체 시스템 장애 상황을 경험하지 않아도 될 것 입니다. resilience4j의 circuit breaker는 3가지의 일반 상태와 2가지의 특수한 상태를 갖습니다.
서킷의 일반적인 상태 3가지
CLOSED : 정상적인 애플리케이션 실행 상태입니다. 이 상태에서는 서킷 브레이커는 실패에 대한 count와 성공에 대한 count를 모니터링합니다. 특정 실패율에 대한 임계값 미만이면 계속 닫힌 상태로 유지되며 요청이 통과될 수 있습니다. 만약 임계값을 초과하게 되면 서킷은 OPEN 상태로 전환됩니다.
OPEN : 차단된 상태를 의미합니다. 회로 차단기처럼 과부하가 발생하면 차단기가 올라가는것을 떠올리면 될 것 같습니다. 서킷 브레이커가 오픈이 되면 요청이 통과되지 못하고 미리 정의된 fallback을 반환하거나 특정 예외를 발생시키게 됩니다. 특정 시간이 지나게 되면 서킷 브레이커는 HALF_OPEN 상태가 되고 제한된 수의 요청이 통과되도록 허용을 함으로써 시스템이 회복되었는지 확인합니다.
HALF_OPEN: 차단된 상태에서 정상적인 상태로 갈 수 있는지 점검하는 상태입니다. 요청이 문제없이 성공하게 되면 회로 차단기는 예외 처리가 회복되었다고 생각하고 다시 닫힌 상태로 전환됩니다. HALF_OPEN 상태에서 요청이 하나라도 실패하게 되면 여전히 시스템에 문제가 있다고 판단하고 다시 OPEN 상태로 돌아갑니다.
특수한 상태 2가지
DISABLED : 항상 액세스 허용
FORCED_OPEN :항상 액세스 거부
이 두 가지 특수 상태는 Circuit Breaker 이벤트가 생성되지 않고 Metric도 기록되지 않습니다.
서킷 브레이커는 sliding window기법을 사용하여 호출 결과를 저장 및 모니터링합니다.
sliding window는 Count-based sliding window와 Time-based sliding window으로 선택할 수 있습니다.
Count-based sliding window : 고정된 숫자만큼의 최신 작업 또는 이벤트를 구성하여 이를 추적합니다. 장점으로는 구현 및 이해가 쉽지만 단점으로는 단시간에 폭증하는 event를 잘 포착하지 못할 수 있습니다.
Time-based sliding window : 고정된 시간만큼의 이벤트를 추적합니다. 예를 들어 10초의 time-based를 구성한다면 지난 10초 동안의 성공 및 실패 횟수를 추적합니다. 장점으로는 시스템 동작의 변화에 좀 더 잘 반응할 수 있지만 단점으로는 sliding window를 관리하는 메커니즘의 복잡성이 높아질 수 있습니다.
서킷 브레이커 설정
@Configuration
class CircuitBreakerConfiguration {
@Bean
fun circuitBreakerConfig(): CircuitBreakerConfig {
return CircuitBreakerConfig.custom()
.failureRateThreshold(0.5)
.waitDurationInOpenState(Duration.ofSeconds(5))
.slidingWindowType(SlidingWindowType.COUNT_BASED)
.slidingWindowSize(10)
.build()
}
@Bean
fun circuitBreakerRegistry(circuitBreakerConfig: CircuitBreakerConfig): CircuitBreakerRegistry {
return CircuitBreakerRegistry.of(circuitBreakerConfig)
}
}
- slidingWindowType : 위에서 언급한 두가지 타입을 설정합니다.
- COUNT_BASED : count 기반
- TIME_BASED : 시간 기반
- slidingWindowSize : 슬라이딩 윈도우 크기
- failureRateThreshold : 서킷브레이커가 열리는 실패율 임계값을 설정합니다.
- waitDurationInOpenState : HALF_OPEN 상태로 전환되기 전 서킷의 OPEN상태를 유지하는 시간을 설정
- minimumNumberOfCalls : 회로 차단기가 열리거나 닫히기 전에 평가해야 하는 최소 호출 수를 설정합니다.
- permittedNumberOfCallsInHalfOpenState : HALF_OPN 상태에서 통과할 수 있는 호출 수를 설정합니다.
- automaticTransitionFromOpenToHalfOpenEnabled : 대기 시간 후 회로 차단기가 OPEN 상태에서 HALF_OPEN상태로 자동 전환되어야 하는지 여부를 구성합니다.
- recordExceptions : 회로 차단기의 상태 전환을 위해 실패로 간주되어야 하는 예외를 구성합니다.
- ringBufferSizeInHalfOpenState : 링 버퍼의 half open 크기 설정
- ringBufferSizeInClosedState : 링 버퍼의 close 크기를 설정
fallback이란?
fallback은 주로 실패가 발생했을 때 대체 작업을 수행하는 메커니즘을 말합니다. Circuit Breaker는 장애가 지속되는 동안 호출을 차단하여 시스템의 부하를 방지하는데, 이때 fallback은 차단된 호출에 대한 대안을 제공합니다. 예를들어 정상응답을 반환할 수 없으니 임시 응답을 반환한다고 생각하면 될것 같습니다.
Resilience4j로 폴백 전략을 구현하려면 두가지 작업이 필요합니다.
1. @CircuitBreaker 또는 다른 애너테이션에 fallbackMehotd 속성을 추가
2. 폴백 메서드를 정의
@CircuitBreaker(name="testService", fallbackMethod="testFallback")
그 외 Resilience4j의 다른 기능들
Rate Limiter : 속도 제한, 서비스가 한번에 수신하는 호출 수를 제한
Rate limiting은 API를 확장 가능하게 하고 서비스의 고가용성과 신뢰성을 확립하는 필수적인 기술입니다. 그러나 이 기술은 검출된 제한 초과를 처리하는 다양한 옵션을 제공합니다. 제한 초과된 요청을 간단히 거부할 수도 있고, 이후에 실행하기 위해 큐를 구축하거나 이러한 두 가지 접근 방식을 어떤 형태로든 결합할 수도 있습니다.
Bulkhead: 과부하를 피하고자 동시 호출하는 서비스 요청 수를 제한
Resilience4j은 동시 실행을 제한하는 두 가지 종류의 Bulkhead 패턴 구현을 제공합니다.
- Semaphore를 사용하는 SemaphoreBulkhead
- 바운드된 큐와 고정 스레드 풀을 사용하는 FixedThreadPoolBulkhead
세마 포어 벌크 헤드 : 세마포어 격리 방식으로 서비스에 대한 동시 요청수를 제한함
스레드 풀 벌크 헤더 : 제한된 큐와 고정 스레드 풀을 사용한다. 이 방식은 풀과 큐가 다 찬 경우메나 요청을 거부함
- maxWaitDuration: 벌크 헤드에 들어갈 때 스레드를 차단할 최대 시간을 설정
- maxConcurrentCalls : 벌크 헤드에서 허용되는 최대 동시 호출 수를 설정
- maxThreadPoolSize : 최대 스레드 풀 크기를 설정한다.
- coreThreadPoolSize : 코어 스레드 풀 크기를 설정한다.
- queCapacity : 큐 용량을 설정
- keepAliveDuration : 스레드가 종료되기 전에 새 작업을 기다리는 최대 시간을 설정
retry : 서비스가 일시적으로 실패할 때 재시도한다.
- maxRetryAttempts : 재시도 최대 횟수
- waitDuration : 재시도 대기 시간
- retry-exceptions : 재시도 대상이 되는 예외 목록
Reference
- 공식 문서 : https://resilience4j.readme.io/
https://github.com/resilience4j/resilience4j/blob/1.7/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfig.java
- 스프링 마이크로 서비스 코딩 공작소 개정 2판(길벗)
'Spring' 카테고리의 다른 글
[spring] @Transactional과 rollback에 대해서 (0) | 2023.11.16 |
---|---|
[Spring] 스프링 어노테이션 정리 (0) | 2020.08.02 |
스프링과 스프링 부트 (0) | 2020.03.28 |