※ 시리즈 글
Resilience4j CircuitBreaker, 직접 구현하면서 이해해보자! - 1편: 이해와 설계 (현재 글)
Resilience4j CircuitBreaker, 직접 구현하면서 이해해보자! - 2편: 상태 머신, 상태 전이
Resilience4j CircuitBreaker, 직접 구현하면서 이해해보자! - 3편: 슬라이딩 윈도우
Resilience4j CircuitBreaker, 직접 구현하면서 이해해보자! - 4편: 설정, 사용, Fallback
🔷 들어가며
외부 API를 호출하는 서비스에는 생각보다 쉽게 문제가 발생합니다. 평소에는 빠르게 응답하던 외부 API가 갑자기 느려지거나, 일시적으로 장애가 나면 어떻게 될까요? 우리 서비스도 그 응답을 기다리느라 처리 속도가 줄어들고, 어느 순간 전체 트래픽이 쌓여 내부 서비스까지 영향을 받는 상황이 옵니다. 즉, 외부의 작은 장애가 내부의 큰 장애로 번지는 문제가 생깁니다.
이 상황은 마치 서울역 화장실에서 긴 줄이 생기는 모습과 비슷합니다. 보통은 금방 들어갈 수 있지만, 갑자기 화장실 안에서 문제가 생기거나 가려는 사람이 많아서 줄이 길게 늘어난다고 해봅시다. 줄이 복도까지 차오르고, 결국 그 앞에 있는 상점들 출입 동선까지 막아버립니다. 화장실 하나가 문제가 생겼는데, 주변 공간 전체가 함께 불편해지는 것입니다.
이럴 때는 때 대응 방법은 크게 두 가지입니다.
- 화장실 입구에서 더 이상 줄을 서지 못하게 막는다. (내부 공간을 보호하는 방식)
- 사람들을 다른 층의 화장실로 안내한다. (우회 처리를 통해 전체 흐름을 유지하는 방식)
CircuitBreaker도 이와 똑같은 역할을 합니다. 외부 API가 문제가 생기면, 우리 서비스까지 영향을 받지 않도록 요청 흐름을 통제하고 우회시키는 보호막 역할을 제공합니다.
이 시리즈에서는 Resilience4j의 CircuitBreaker가 무엇인지 알아보고, 직접 구현해 보면서 어떻게 동작하고 설계되었는지 알아보겠습니다. 시리즈 글은 다음과 같이 진행될 예정입니다.
구현된 코드는 Spring Boot 3.5.7, Kotlin으로 작성되어 있습니다. 아래를 참고해 주세요.
spring-lab/resilience4j-circuitbreaker at main · alstn113/spring-lab
Spring에 대한 다양한 실험. Contribute to alstn113/spring-lab development by creating an account on GitHub.
github.com
🔷 Resilience4j CircuitBreaker란 무엇인가?
Resilience4j는 함수형 프로그래밍을 위해 설계된 경량 장애 허용(Fault Tolerance) 라이브러리입니다. Resilience4j에는 CircuitBreaker 모듈뿐만 아니라 Bulkhead, RateLimiter, Retry, TimeLimiter, Cache 등 다양한 모듈이 존재합니다. 여기서 저희가 집중적으로 볼 부분은 CircuitBreaker입니다.
🔶 CircuitBreaker의 상태
CircuitBreaker는 크게 CLOSED, OPEN, HALF_OPEN이라는 3가지 상태를 가집니다.

- CLOSED 상태는 정상적인 상태로, 모든 호출이 허용됩니다. 이 상태에서는 요청들의 결과를 기록하며, 실패율이 설정한 임계치를 넘어가면 OPEN 상태로 전환됩니다.
- OPEN 상태는 장애가 발생한 상태로, 모든 호출이 차단됩니다. 일정 시간이 지나면 HALF_OPEN 상태로 전환되어 제한적으로 호출을 허용합니다.
- HALF_OPEN 상태에서는 일부 호출만 허용하고, 그 결과를 바탕으로 상태를 결정합니다.
- 호출 결과가 정상적이면 CLOSED로 전환됩니다.
- 호출 결과가 여전히 문제가 있으면 OPEN 상태로 다시 전환됩니다.
추가적으로, DISABLED, METRICS_ONLY, FORCED_OPEN 상태가 있습니다.
🔶 CircuitBreaker의 슬라이딩 윈도우
위에서 설명했듯이, CircuitBreaker는 호출 결과를 기록하고, 실패율을 계산해서 상태를 전환합니다. 그런데 모든 호출 기록을 무한히 저장할 수 없기 때문에, 최근 일정 범위의 호출만 기록하고 평가하는 방법을 사용합니다. 이때 사용하는 방법이 슬라이딩 윈도우입니다.
슬라이딩 윈도우는 두 가지 방식을 지원하고 있습니다.
- Count-based (횟수 기반) 윈도우
- 최근 N번의 호출 결과만 기록합니다.
- 새로운 호출이 들어오면 가장 오래된 기록을 제거하고, 새로운 결과를 추가합니다.
- 예: 최근 100번 호출 중 60번 실패 -> 실패율 60%
- Time-based (시간 시간) 윈도우
- 최근 N초 동안의 호출 결과를 기록합니다.
- 시간이 지나면 오래된 기록은 덮어쓰기 됩니다.
- 예: 최근 60초 동안 100번 호출 중 20번 실패 -> 실패율 20%
CircuitBreaker는 이 슬라이딩 윈도우를 활용해 실패율을 계산하고, 실패율이 임계치를 넘으면 OPEN 상태로 전환합니다. 반대로 실패율이 낮으면 CLOSED 상태를 유지합니다.
여기서 슬라이딩 윈도우 타입을 선택하는 것은 CLOSED 상태에서를 말합니다. HALF_OPEN은 제한된 호출만 허용하기 때문에 Count-based 방식만 사용됩니다.
🔶 CircuitBreaker의 설정
Circuit Breaker는 다양한 설정을 통해 동작 방식을 조절할 수 있습니다. 각 CircuitBreaker에 대해서 개별적으로 설정할 수도 있고, 기본값에 대해서도 설정할 수 있습니다. 또한 코드를 통해 직접 설정할 수도 있고, application.yml을 통해서 CircuitBreaker를 등록할 수도 있습니다.
| 설정값 | 기본값 | 설명 |
| failureRateThreshold | 50 | 실패율 임계치 기준 |
| slowCallRateThreshold | 100 | 느린 호출 비율 기준 |
| slowCallDurationThreshold | 60s | 느린 호출 기준 시간 |
| permittedNumberOfCallsInHalfOpenState | 10 | HALF_OPEN에서 테스트 호출 수 |
| maxWaitDurationInHalfOpenState | 0 | HALF_OPEN 유지 최대 시간 (0 = 무제한) |
| slidingWindowType | COUNT_BASED | 집계 방식 |
| slidingWindowSize | 100 | 슬라이딩 윈도우 크기 |
| minimumNumberOfCalls | 100 | 집계 최소 호출 수 |
| waitDurationInOpenState | 60s | OPEN 상태 유지 시간 |
| automaticTransitionFromOpenToHalfOpenEnabled | false | 자동 HalfOpen 전환 여부 |
| recordExceptions | [] | 실패로 처리할 예외 목록 |
| ignoreExceptions | [] | 무시할 예외 목록 |
코드를 통해 CircuitBreaker를 설정하고 생성하는 방법입니다.
// 코드를 통해 설정하고 생성하는 방법
val config = CircuitBreakerConfig.custom()
.failureRateThreshold(50f)
.waitDurationInOpenState(Duration.ofMillis(1000))
.permittedNumberOfCallsInHalfOpenState(2)
.slidingWindowSize(2)
.recordExceptions(IOException::class.java, TimeoutException::class.java)
.ignoreExceptions(BusinessException::class.java, OtherBusinessException::class.java)
.build()
val registry = CircuitBreakerRegistry.of(circuitBreakerConfig)
val circuitBreaker = registry.circuitBreaker("name"); // 생성 또는 조회
// 기본값을 통해 생성 또는 설정과 함께 생성이 가능함.
application.yml을 통해 CircuitBreaker를 설정하고 생성하는 방법입니다.
# application.yml을 통한 설정
resilience4j.circuitbreaker:
instances:
backendA:
registerHealthIndicator: true
slidingWindowSize: 100
backendB:
registerHealthIndicator: true
slidingWindowSize: 10
permittedNumberOfCallsInHalfOpenState: 3
slidingWindowType: TIME_BASED
minimumNumberOfCalls: 20
waitDurationInOpenState: 50s
failureRateThreshold: 50
eventConsumerBufferSize: 10
recordFailurePredicate:
🔶 CircuitBreaker의 사용과 FallbackMethod
CircuitBreaker는 Spring에서 어노테이션을 통해서 편하게 사용할 수 있습니다. @CircuitBreaker의 name을 통해 특정 CircuitBreaker를 지정하여 사용할 수 있습니다.
@RestController
class TestController {
private val random = Random()
@GetMapping("/dns")
@CircuitBreaker(name = "myCircuitBreaker", fallbackMethod = "fallback")
fun queryDns(@RequestParam prob: Double): String {
return if (random.nextDouble() < prob) {
"Cloudflare DNS 응답 성공 (1.1.1.1)"
} else {
throw IllegalArgumentException()
}
}
private fun fallback(prob: Double, e: CallNotPermittedException): String {
return "Cloudflare DNS 차단됨 → Google DNS(8.8.8.8)로 우회"
}
private fun fallback(prob: Double, e: IllegalArgumentException): String {
return "Cloudflare DNS 실패 → 요청 실패 응답"
}
}
@CircuitBreaker의 fallbackMethod는 대상 메서드에서 예외가 발생하거나 CircuitBreaker가 OPEN 상태일 때 실행될 메서드를 지정합니다. 중요한 점은 예외가 발생했을 때도 항상 호출된다는 것입니다.
- 메서드 시그니처: fallback 메서드는 대상 메서드와 동일한 파라미터를 가져야 하며, 마지막 파라미터로 예외 타입을 받을 수 있습니다.
- OPEN 상태 처리: CircuitBreaker는 CallNotPermittedException 예외가 발생하고, 이 경우에도 fallback 메서드가 호출됩니다.
- 예외 매칭: 지정한 예외가 없으면 상위 타입의 예외를 찾아서 처리합니다. 이를 통해 CircuitBreaker가 OPEN 상태인지, 실제 예외가 발생했는지 구분할 수 있습니다.
🔷 구현할 범위 한정하기
지금까지 Resilience4j CircuitBreaker의 기본 구조와 사용 방법에 대해 살펴보았습니다. 실제 라이브러리에는 여기서 다룬 것 외에도 다양한 설정과 기능이 존재합니다. 하지만 이번 시리즈에서는 핵심적인 기능에 집중하기 위해 구현 범위를 한정하고, 꼭 필요한 부분만 직접 구현해 보겠습니다.
직접 구현할 부분은 다음과 같습니다.
- CLOSED, OPEN, HALF_OPEN 상태
- Count-based, Time-based 윈도우
- application.yml을 통한 설정과 생성 그리고 fallback 처리
🔷 기본 틀 설계하기
우선 구현 결과를 미리 보자면 다음과 같습니다. 크게 Core, Metrics, Spring 부분으로 패키지를 분리할 것입니다.

- Core: CircuitBreaker의 설정과 상태 머신, 상태 전이에 관련된 핵심 코드가 포함됩니다.
- Metrics: Count-based와 Time-based 슬라이딩 윈도우 구현과, 실패율을 집계하는 코드가 포함됩니다.
- Spring: Spring 환경에서 CircuitBreaker를 사용할 수 있도록 지원하며, 어노테이션 기반 사용과 fallback 메서드 처리를 담당합니다.
🔷 마치며
이번 글에서는 Resilience4j CircuitBreaker의 기본적인 이해와 구조를 살펴보았습니다. 다음 글인 2편: 상태 머신, 상태 전이에서는 상태 머신이 어떻게 구현되어 있고, 어떻게 상태 전이가 발생하는지 코드를 보면서 구체적으로 알아보도록 하겠습니다. 잘못된 부분이 있다면 댓글로 남겨주시길 바랍니다. 읽어주셔서 감사합니다.
🔷 참고
'서버' 카테고리의 다른 글
| Resilience4j CircuitBreaker, 직접 구현하면서 이해해보자! - 3편: 슬라이딩 윈도우 (1) | 2025.11.21 |
|---|---|
| Resilience4j CircuitBreaker, 직접 구현하면서 이해해보자! - 2편: 상태 머신, 상태 전이 (0) | 2025.11.21 |
| Redis 분산락, 직접 구현하면서 이해해보자! - 2편: Pub/Sub 방식 (0) | 2025.10.31 |
| Redis 분산락, 직접 구현하면서 이해해보자! - 1편: Spin Lock 방식 (0) | 2025.10.31 |
| POST 요청의 중복 처리를 막는 멱등키 헤더 구현 (Interceptor + Redis) (0) | 2025.09.17 |
