서킷브레이커 패턴이란 외부 서비스에 의한 문제를 방지하기 위해 등장한 디자인 패턴으로 문제가 발생한 지점을 감지하고 실패하는 요청을 계속하지 않도록 방지합니다. 그리고 이를 통해 시스템의 장애 확산을 막고 장애 복구를 도와주며 유저는 불필요하게 대기하지 않게 됩니다. 가정집에 있는 누전차단기가 화재를 막는 것과 비슷하게 CircuitBreaker(직역하면 회로차단기)는 서비스의 장애 전파를 막는다고 이해하면 됩니다.
아래 그림과 같이 ServiceA가 ServiceB를 호출 할 때 ServiceB가 반복적으로 실패한다면 CircuitBreaker 를 Open 하여 ServiceB에 대한 흐름을 차단하는게 서킷브레이커의 역할입니다. * CircuitBreaker 의 Open 은 흐름을 차단하는 것으로, 흐름을 열어둔다(opened) 라는 의미가 아닙니다. *반대로 CircuitBreaker 의 Closed 상태는 흐름을 허용하는 정상상태를 의미합니다.
이를 알아보기 위해 resilience4j jar의 CircuitBreakerConfigurationProperties, RetryConfigurationProperties 클래스 내부를 살펴보면,
CircuitBreaker 와 Retry 의 Order 값이 각각 -3, -4 로
별도 처리가 없을 경우 CircuitBreaker 가 Retry 보다 우선으로 적용됨을 알 수 있습니다.
CircuitBreakerConfigurationProperties
public class CircuitBreakerConfigurationProperties extends
io.github.resilience4j.common.circuitbreaker.configuration.CircuitBreakerConfigurationProperties {
private int circuitBreakerAspectOrder = Ordered.LOWEST_PRECEDENCE - 3;
...
}
RetryConfigurationProperties
public class RetryConfigurationProperties extends
io.github.resilience4j.common.retry.configuration.RetryConfigurationProperties {
private int retryAspectOrder = Ordered.LOWEST_PRECEDENCE - 4;
...
}
CircuitBreakerAspect
@Aspect
public class CircuitBreakerAspect implements Ordered {
...
@Override
public int getOrder() {
return circuitBreakerProperties.getCircuitBreakerAspectOrder();
}
}
AOP 기반하에 동작하므로 우선순위를 바꿔서 적용하고자 할 경우 annotation 방식을 사용하여 layer 를 분리하거나 aspectOrder 속성값을 수정하여 적용할 수 있습니다.
Resilience4j Configuration
Resilience4j 의 Configuration 은 yml 파일을 사용하거나, java 코드를 통해 설정할 수 있습니다.
@Configuration
class CircuitBreakerProvider(
val circuitBreakerRegistry: CircuitBreakerRegistry,
) {
companion object {
const val CIRCUIT_MEMDB: String = "CB_MEMDB"
}
@Bean
fun memDBCircuitBreaker(): CircuitBreaker {
return circuitBreakerRegistry.circuitBreaker(
CIRCUIT_MEMDB, CircuitBreakerConfig.custom()
.failureRateThreshold(10F) // 실패비율 10% 이상시 서킷 오픈
.slowCallDurationThreshold(Duration.ofMillis(500)) // 500ms 이상 소요시 실패로 간주
.slowCallRateThreshold(10F) // slowCallDurationThreshold 초과 비율이 10% 이상시 서킷 오픈
.waitDurationInOpenState(Duration.ofMillis(60000)) // OPEN -> HALF-OPEN 전환 전 기다리는 시간
.minimumNumberOfCalls(5) // 집계에 필요한 최소 호출 수
.slidingWindowSize(5) // 서킷 CLOSE 상태에서 5회 호출 도달시 failureRateThreshold 실패비율 계산
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED) // 호출 횟수 기준 계산 (TIME_BASED는 시간 기준)
.ignoreExceptions(StockManageException::class.java) // 화이트리스트로 서킷 오픈 기준 ex 관리
.build()
)
}
"Resilience4j 모듈 중 가장 많이 사용되는 CircuitBreaker, Retry 모듈의 속성값에 대해 간단히 알아보겠습니다."
Resilience4j CircuitBreaker Property
property설명
failureRateThreshold
실패비율 임계치를 백분율로 설정 해당 값을 넘어갈 시 Circuit Breaker 는 Open상태로 전환되며, 이때부터 호출을 차단한다 (기본값: 50)
slowCallRateThreshold
임계값을 백분율로 설정, CircuitBreaker는 호출에 걸리는 시간이 slowCallDurationThreshold보다 길면 느린 호출로 간주, 해당 값을 넘어갈 시 Circuit Breaker 는 Open상태로 전환되며, 이때부터 호출을 차단한다 (기본값: 100)
slowCallDurationThreshold
호출에 소요되는 시간이 설정한 임계치보다 길면 느린 호출로 계산한다. -> 응답시간이 느린것으로 판단할 기준 시간 (60초, 1000 ms = 1 sec) (기본값: 60000[ms])
permittedNumberOfCallsInHalfOpenState
HALF_OPEN 상태일 때, OPEN/CLOSE 여부를 판단하기 위해 허용할 호출 횟수를 설정 수 (기본값: 10)
maxWaitDurationInHalfOpenState
HALF_OPEN 상태로 있을 수 있는 최대 시간이다. 0일 때 허용 횟수 만큼 호출을 모두 완료할 때까지 HALF_OEPN 상태로 무한정 기다린다. (기본값: 0)
slidingWindowType
sliding window 타입을 결정한다. COUNT_BASED인 경우 slidingWindowSize만큼의 마지막 call들이 기록되고 집계됩니다. TIME_BASED인 경우 마지막 slidingWindowSize초 동안의 call들이 기록되고 집계됩니다. (기본값: COUNT_BASED)
slidingWindowSize
CLOSED 상태에서 집계되는 슬라이딩 윈도우 크기를 설정한다. (기본값: 100)
minimumNumberOfCalls
minimumNumberOfCalls 이상의 요청이 있을 때부터 faiure/slowCall rate를 계산한다. 예를들어, 해당값이 10이라면 최소한 호출을 10번을 기록해야 실패 비율을 계산할 수 있다. 기록한 호출 횟수가 9번뿐이라면 9번 모두 실패했더라도 circuitbreaker는 열리지 않는다. (기본값: 100)
waitDurationInOpenState
OPEN에서 HALF_OPEN 상태로 전환하기 전 기다리는 시간 (60초, 1000 ms = 1 sec) (기본값: 60000[ms])
recordExceptions
실패로 기록할 Exception 리스트 (기본값: empty)
ignoreExceptions
실패나 성공으로 기록하지 않을 Exception 리스트 (기본값: empty)
ignoreException
기록하지 않을 Exception을 판단하는 Predicate<Throwable>을 설정 (커스터마이징, 기본값: throwable -> true)
recordFailure
어떠한 경우에 Failure Count를 증가시킬지 Predicate를 정의해 CircuitBreaker에 대한 Exception Handler를 재정의하는 것이다. true를 return할 경우, failure count를 증가시키게 된다 (기본값: false)
Resilience4j Retry Property
property설명
maxRetryAttempts
최대 재시도 수(최초 호출도 포함, 기본값 3)
waitDuration
재시도 할 때마다 기다리는 고정시간 (1초[1000ms], 기본값: 0.5초[500ms])
retryOnResultPredicate
반환되는 결과에 따라서 retry를 할지 말지 결정하는 filter, true로 반환하면 retry하고 false로 반환하면 retry 하지 않습니다. (기본값: (numOfAttempts,Either<throwable, result) -> waitDuration)
retryExceptionPredicate
예외(Exception)에 따라 재시도 여부를를 결정하기 위한 filter, 만약 예외에 따라 재시도해야 한다면 true를, 그 외엔 false를 리턴해야 한다. (기본값: result -> false)
retryExceptions
실패로 기록되는 블랙리스트 예외. empty일 경우 모든 에러 클래스를 재시도 한다. (기본값: empty)
ignoreExceptions
무시되어야 하는 예외(화이트리스트) 즉, 재시도 되지 않아야 할 에러 클래스 리스트이다. (기본값: empty)
failAfterMaxRetries
설정한 maxAttempts 만틈 재시도하고 나서도 결과가 여전히 retryOnResultPredicate를 통과하지 못했을 때 MaxRetriesExceededException 발생을 활성화/비활성화하는 boolean (기본값: false)
* 그외 모듈에 대한 속성값이 궁금하시다면 아래의 Resilience4j 공식 document 를 참고해주세요.