
서비스는 태생적으로 장애가 발생할 수 밖에 없습니다.
장애가 없는 서비스란 존재하지 않는다고 해도 과언이 아니죠.
그런 의미에서 Resilience4j는 회복탄력성 패턴을 통해 장애가 발생해도 서비스가 버티게하는 역할을 합니다.
그 내용 그대로 정말 중요한 역할을 하는 패턴이죠.
자세히 한 번 알아보겠습니다.
우리가 개발하는 서비스는 마이크로서비스일 확률이 매우 높습니다.
각각의 서비스는 서로 유기적인 연결을 통해 하나의 통합된 서비스를 제공하죠.
그 의미는 외부 의존성(결제, 배송, 추천, 검색 등 타 서비스 API를 호출)을 가진 서비스들의 통신은 언젠가 반드시 실패할 가능성이 매우 큽니다.
여기서 Resilience4j는 회복탄력성 패턴으로 '실패를 없애는게 아니라 무너지지 않게 버티는 구조'를 만들어냅니다.
본격적으로 내용을 시작하기 전 용어를 간략하게 정리하고 넘어가겠습니다.
우리가 일반적으로 장애를 격리하지 않고 그대로 전파되도록 둔다면 다음과 같은 연쇄적인 반응이 발생하게 될 것입니다.

C가 느려지면 B의 호출에서 병목이 생기고 B를 부르는 A의 스레드도 트랜잭션으로 묶여버립니다.
A의 스레드가 묶이면 사용자의 요청이 지속적으로 쌓이고, 결국 전체가 같이 느려지거나 다운되어버리는 연쇄적인 장애가 발생하게 됩니다.
이런 환경에서 Resilience4j를 활용한 회복탄력성 패턴을 적용시키면 아래와 같이 장애를 격리하여 업스트림으로의 전파를 막을 수 있습니다.

여기서 핵심은 느린 호출을 빨리 끊어내고(TimeLimiter/Timeout) 반복 실패는 일정 비율이 넘으면 차단하고(CircuitBreaker) 장애의 폭주가 전체를 잡아먹지 않도록 격리(Bulkhead/RateLimiter) 하도록 처리해야 합니다.
각각의 패턴을 간략하게 정리해보겠습니다.
| 패턴 | 한 줄 설명 | 주로 막는 문제 | 대표 설정 |
|---|---|---|---|
| CircuitBreaker | “망가진 곳에 잠깐 연락 끊자” | 반복 실패/지연으로 인한 전파 | failureRateThreshold, waitDurationInOpenState |
| Retry | “일시적이면 몇 번 더 시도” | 순간 오류/짧은 5xx | maxAttempts, waitDuration |
| TimeLimiter | “너무 오래 걸리면 끊자” | 느림으로 인한 스레드 고갈 | timeoutDuration |
| RateLimiter | “초당 N건까지만 허용” | 과도한 호출/보호 | limitForPeriod, limitRefreshPeriod |
| Bulkhead | “동시 실행을 제한하거나 분리” | 특정 기능 폭주로 전체 장애 | maxConcurrentCalls / thread pool |
제공되는 패턴을 실무에선 적절히 혼합하여 사용하는데 간단한 예시로는 Bulkhead + TimeLimiter + CircuitBreaker 패턴을 사용하는 방식 등을 들 수 있습니다.
Resilience4j는 다음과 같이 설정할 수 있습니다.
Maven 환경과 Gradle환경에서는 종속성을 추가해주면 됩니다.
<Maven 방식>
<!-- Resilience4j for Spring Boot 3 -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
<Gradle 방식>
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'io.github.resilience4j:resilience4j-spring-boot3'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
그럼 Resilience4j 설정은 어떤식으로 하게 될까요?
일반적으로 실무에선 application.yml에 작성하여 사용하며 Config Server와도 잘 맞습니다.
Resilience4j에서 가장 유명하며 잘 알려져있는 패턴인 CircuitBreaker 패턴에 대해서 알아보도록 하겠습니다.

그럼 CircuitBreaker가 하는 일에 대해서 알아보겠습니다.
슬라이딩 윈도우 설정에 따라 최근 N개의 호출을 관찰하게 됩니다. 이후 특정 조건 상에서 실패율이나 Latency 비율이 임계치 이상이면 OPEN으로 상태가 전환됩니다.
상태가 OPEN인 동안 호출을 막아 추가 피해를 줄이고 일정 시간 후 HALF_OPEN으로 전환하여 시스템이 회복되었는지를 테스트 하게 됩니다.
slidingWindowSize=10, minimumNumberOfCalls=5라면일반적으로 application.yml설정을 하게 되면 아래와 같이 설정을 하게 됩니다.
참고로 instances의 이름과 어노테이션의 name은 정확히 일치해야 합니다.
resilience4j:
circuitbreaker:
configs:
default:
slidingWindowType: COUNT_BASED
slidingWindowSize: 50
minimumNumberOfCalls: 20
failureRateThreshold: 50
slowCallRateThreshold: 50
slowCallDurationThreshold: 2s
waitDurationInOpenState: 10s
permittedNumberOfCallsInHalfOpenState: 10
automaticTransitionFromOpenToHalfOpenEnabled: true
recordExceptions:
- java.io.IOException
- org.springframework.web.client.ResourceAccessException
ignoreExceptions:
- com.example.demo.exception.BusinessException
instances:
paymentApi:
baseConfig: default
간단하게 설정 필드들을 확인해보면 아래와 같습니다.
slidingWindowSize: 관찰할 호출 개수(예: 최근 50번)minimumNumberOfCalls: 최소 몇 번은 호출되어야 판단 시작할 것인지에 대한 값 설정failureRateThreshold: 실패 비율 % 기준slowCallDurationThreshold: 지연으로 볼 시간 기준waitDurationInOpenState: OPEN 유지 시간permittedNumberOfCallsInHalfOpenState: HALF_OPEN에서 테스트 호출 수recordExceptions/ignoreExceptions는 실패율 계산에 어떤 예외를 포함할지를 결정합니다. ignoreExceptions에 둡니다.위와 같이 설정을 해두면 Resilience4j의 CircuitBreaker는 사용할 준비가 끝나게 됩니다.
이를 실제로 사용할 때엔 다음과 같이 어노테이션 기반으로 사용하게 됩니다.
@Service
public class PaymentClient {
private final RestTemplate restTemplate;
public PaymentClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@CircuitBreaker(
name = "paymentApi", // 어노테이션의 name은 여기를 말합니다.
fallbackMethod = "fallback"
)
public PaymentResponse requestPayment(PaymentRequest req) {
return restTemplate.postForObject(
"http://payment-service/pay",
req,
PaymentResponse.class
);
}
// fallbackMethod 규칙:
// - 원본 파라미터 그대로 + 마지막에 Throwable(선택)
private PaymentResponse fallback(PaymentRequest req, Throwable t) {
return PaymentResponse.fail("PAYMENT_TEMPORARILY_UNAVAILABLE");
}
}
만약 이전 상황으로 인해 CircuitBreaker가 OPEN상태라면 실제 호출은 실행되지 않고 즉시 차단되게 됩니다.
이때 fallbackMethod가 호출되게되고 Throwable에는 보통 CallNotPermittedException이 발생됩니다.
이후 설정해놓은 차단시간(waitDurationInOpenState)이 지나면 Resilience4j는 자동으로 상태를 OPEN에서 HALE_OPEN으로 변경하게 됩니다.
다만 automaticTransitionFromOpenToHalfOpenEnabled=false로 설정되어있다면 이땐 다음 호출이 들어올 때 HALF_OPEN으로 상태가 변경됨을 뜻합니다.
오늘은 Resilience4j의 가장 대표적인 패턴인 CircuitBreaker 패턴에 대해 스터디를 해보았습니다.
각 설정 필드들이 어떤 효과를 가지고 있는지 알 수 있었습니다 😃
다음 글에선 Retry나 TimeLimiter, Bulkhead 방식 등 좀 더 다양한 패턴에 대해 정리를 해보도록 하겠습니다!
읽어주셔서 감사합니다 🫡