Spring Boot 3 환경에서 Resilience4j를 활용하여 마이크로서비스의 장애 대응력을 높이는 방법을 단계별로 설명합니다.

Circuit Breaker, Rate Limiter, Retry 등 주요 기능의 적용 예제와 실전 팁을 제공합니다.

들어가며

마이크로서비스 아키텍처는 서비스 간의 통신이 빈번하게 일어나기 때문에, 한 서비스의 장애가 연쇄적으로 다른 서비스에 영향을 미칠 수 있습니다. 이를 방지하기 위해 Resilience4j와 같은 장애 대응 라이브러리를 사용하는 것이 필수적입니다. 본 포스트에서는 Spring Boot 3 환경에서 Resilience4j를 어떻게 적용하고, 각 기능을 효과적으로 사용하는지 초보자도 쉽게 따라할 수 있도록 설명합니다.

Resilience4j란?

Resilience4j는 Java 8+ 및 Kotlin 환경에서 사용할 수 있는 경량 장애 대응 라이브러리입니다. Netflix의 Hystrix가 더 이상 유지보수되지 않으면서, Spring 진영에서는 Resilience4j가 표준처럼 자리잡았습니다.

  • 주요 기능
    • Circuit Breaker(회로 차단기)
    • Rate Limiter(요청 제한)
    • Retry(재시도)
    • Bulkhead(격벽)
    • TimeLimiter(시간 제한)

Spring Boot 3에서 Resilience4j 적용하기

1. 의존성 추가

Spring Boot 3 프로젝트의 build.gradle 또는 pom.xml에 아래와 같이 의존성을 추가합니다.

dependencies {
    implementation("io.github.resilience4j:resilience4j-spring-boot3:2.0.2")
    implementation("org.springframework.boot:spring-boot-starter-actuator")
}
2. 기본 설정 (자세히)

application.yml 또는 application.properties에 Resilience4j의 Circuit Breaker 등 주요 기능의 설정을 추가합니다. 각 옵션의 의미와 실전에서 자주 사용하는 설정 예시를 함께 설명합니다.

resilience4j:
  circuitbreaker:
    configs:
      default:
        registerHealthIndicator: true          # 헬스체크 엔드포인트에 Circuit Breaker 상태 노출
        slidingWindowType: COUNT_BASED         # 윈도우 타입: COUNT_BASED(호출 횟수 기준) 또는 TIME_BASED(시간 기준)
        slidingWindowSize: 10                  # 윈도우 내 호출 횟수(또는 기간)
        minimumNumberOfCalls: 5                # 통계 산출을 위한 최소 호출 수
        permittedNumberOfCallsInHalfOpenState: 3  # Half-Open 상태에서 허용할 호출 수
        failureRateThreshold: 50               # 실패율 임계값(%) - 초과 시 Open 상태로 전환
        waitDurationInOpenState: 10s           # Open 상태 유지 시간(예: 10초)
        slowCallRateThreshold: 100             # 느린 호출 비율 임계값(%)
        slowCallDurationThreshold: 2s          # 느린 호출로 간주할 기준 시간(예: 2초)
        automaticTransitionFromOpenToHalfOpenEnabled: true  # Open → Half-Open 자동 전환 활성화
        recordExceptions:                      # 실패로 간주할 예외 목록
          - org.springframework.web.client.HttpServerErrorException
          - java.io.IOException
        ignoreExceptions:                      # 무시할 예외 목록
          - com.example.exception.IgnoreMeException
    instances:
      myService:
        baseConfig: default                    # 위에서 정의한 default 설정 사용
주요 옵션 설명
  • registerHealthIndicator: Actuator health endpoint에 Circuit Breaker 상태를 노출합니다.
  • slidingWindowType: COUNT_BASED(호출 횟수 기준) 또는 TIME_BASED(시간 기준)로 통계 윈도우 타입을 지정합니다.
  • slidingWindowSize: 윈도우 내에 포함될 호출 횟수(또는 시간 단위).
  • minimumNumberOfCalls: 실패율 계산에 필요한 최소 호출 수.
  • permittedNumberOfCallsInHalfOpenState: Half-Open 상태에서 테스트로 허용할 호출 수.
  • failureRateThreshold: 실패율이 이 값을 넘으면 Circuit Breaker가 Open 상태로 전환됩니다.
  • waitDurationInOpenState: Open 상태를 유지할 시간(이후 Half-Open으로 자동 전환).
  • slowCallRateThreshold, slowCallDurationThreshold: 느린 호출의 임계값과 비율.
  • automaticTransitionFromOpenToHalfOpenEnabled: Open에서 Half-Open으로 자동 전환할지 여부.
  • recordExceptions, ignoreExceptions: 실패로 기록할 예외, 무시할 예외를 지정합니다.
실전 팁
  • 서비스별로 instances 아래에 여러 인스턴스를 정의할 수 있습니다.
  • 각 인스턴스마다 별도의 설정을 줄 수도 있고, 공통 설정(baseConfig)을 상속받을 수도 있습니다.
  • Circuit Breaker 외에도 retry, ratelimiter, bulkhead 등도 유사하게 설정할 수 있습니다.
서비스별 인스턴스 분리 예시

실제 마이크로서비스 환경에서는 하나의 애플리케이션에서 여러 외부 시스템이나 API를 호출하는 경우가 많습니다. 이때 각 외부 서비스별로 별도의 Circuit Breaker 인스턴스를 정의하면, 한 서비스의 장애가 다른 서비스에 영향을 주지 않도록 격리할 수 있습니다.

예를 들어, userServiceorderService라는 두 외부 API를 호출한다면 다음과 같이 설정할 수 있습니다.

resilience4j:
  circuitbreaker:
    configs:
      default:
        slidingWindowSize: 10
        failureRateThreshold: 50
        waitDurationInOpenState: 10s
    instances:
      userService:         # 사용자 API용 Circuit Breaker 인스턴스
        baseConfig: default
        failureRateThreshold: 40   # 사용자 서비스에 더 엄격한 임계값 적용
      orderService:        # 주문 API용 Circuit Breaker 인스턴스
        baseConfig: default
        waitDurationInOpenState: 20s   # 주문 서비스는 Open 상태를 더 길게 유지

이렇게 하면 @CircuitBreaker(name = "userService"), @CircuitBreaker(name = "orderService")처럼 서비스별로 다른 Circuit Breaker 정책을 적용할 수 있습니다. 장애 상황이 발생해도 각 인스턴스가 독립적으로 동작하므로, 한 서비스의 장애가 전체 시스템에 영향을 주는 것을 방지할 수 있습니다.

참고 예시: Retry 설정
resilience4j:
  retry:
    configs:
      default:
        maxAttempts: 3                   # 최대 재시도 횟수
        waitDuration: 2s                 # 재시도 간 대기 시간
        retryExceptions:
          - java.io.IOException
        ignoreExceptions:
          - com.example.exception.IgnoreMeException
    instances:
      myRetry:
        baseConfig: default
3. Circuit Breaker 사용 예제

다음은 외부 API를 호출하는 서비스에 Circuit Breaker를 적용하는 예제입니다.

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class ExternalApiService(private val restTemplate: RestTemplate) {
    @CircuitBreaker(name = "myService", fallbackMethod = "fallback")
    fun callExternalApi(): String {
        return restTemplate.getForObject("https://api.example.com/data", String::class.java) ?: ""
    }

    fun fallback(ex: Throwable): String {
        return "외부 API 호출 실패, 기본값 반환"
    }
}
4. Retry, RateLimiter, Bulkhead 적용 예시

Resilience4j는 Circuit Breaker 외에도 다양한 장애 대응 패턴을 제공합니다. 아래는 주요 어노테이션 사용 예시입니다.

import io.github.resilience4j.retry.annotation.Retry
import io.github.resilience4j.ratelimiter.annotation.RateLimiter
import io.github.resilience4j.bulkhead.annotation.Bulkhead

@Service
class RobustService {
    @Retry(name = "myService", fallbackMethod = "fallback")
    @RateLimiter(name = "myService")
    @Bulkhead(name = "myService")
    fun process(): String {
        // 외부 시스템 호출 또는 시간이 오래 걸릴 수 있는 작업
        ...
        return "정상 처리 결과"
    }

    fun fallback(ex: Throwable): String {
        return "장애 발생, 기본값 반환"
    }
}
5. 모니터링 및 Actuator 통합

Spring Boot Actuator와 연동하면 Circuit Breaker 상태를 쉽게 모니터링할 수 있습니다.

build.gradle에 이미 spring-boot-starter-actuator가 포함되어 있다면, /actuator/health, /actuator/circuitbreakerevents 엔드포인트에서 상태와 이벤트를 확인할 수 있습니다.

management:
  endpoints:
    web:
      exposure:
        include: health,info,circuitbreakerevents

여기서 circuitbreakerevents를 추가하면, Actuator의 /actuator/circuitbreakerevents 엔드포인트에서 Circuit Breaker의 상태 변화, 성공/실패, Open/Close 등 다양한 이벤트 로그를 실시간으로 확인할 수 있습니다.

예시 응답(JSON):

{
  "circuitBreakerEvents": [
    {
      "circuitBreakerName": "userService",
      "type": "SUCCESS",
      "creationTime": "2025-06-08T16:20:00.123+09:00",
      "eventType": "SUCCESS"
    },
    {
      "circuitBreakerName": "userService",
      "type": "ERROR",
      "creationTime": "2025-06-08T16:20:01.456+09:00",
      "eventType": "ERROR"
    },
    {
      "circuitBreakerName": "userService",
      "type": "STATE_TRANSITION",
      "creationTime": "2025-06-08T16:20:02.789+09:00",
      "eventType": "STATE_TRANSITION",
      "stateTransition": "CLOSED_TO_OPEN"
    }
  ]
}

이처럼 각 Circuit Breaker 인스턴스별로 성공, 실패, 상태 전이 등의 이벤트가 기록되어, 장애 발생 시점과 원인을 빠르게 파악할 수 있습니다. 운영 환경에서는 이 데이터를 기반으로 알림이나 대시보드 연동도 가능합니다.

6. 실전 적용 팁
  • 각 서비스별로 별도의 Circuit Breaker 인스턴스를 사용하세요.
  • Fallback 메서드는 비즈니스적으로 안전한 기본값을 반환하도록 설계해야 합니다.
  • 장애 상황을 테스트하려면 임의로 예외를 발생시키는 Mock API를 활용해보세요.
  • RateLimiter, Bulkhead, TimeLimiter 등은 실제 트래픽 상황에 맞게 조정해야 합니다.
  • Actuator와 연동하여 실시간 모니터링 및 알림을 설정하면 운영 안정성이 높아집니다.

주요 개념 심화 설명

Circuit Breaker란?

Circuit Breaker는 외부 시스템에서 장애가 일정 비율 이상 발생하면 회로를 열어 더 이상 요청을 보내지 않고, 일정 시간 후 회로를 반쯤 닫아(half-open) 정상 복구 여부를 확인합니다. 이를 통해 장애의 전파를 막고, 시스템 전체의 안정성을 높입니다.

Retry란?

일시적인 네트워크 오류나 장애가 발생했을 때, 자동으로 재시도를 수행하여 성공 확률을 높입니다. 단, 무한 재시도는 오히려 시스템에 부담을 줄 수 있으므로, 재시도 횟수와 대기 시간을 적절히 설정해야 합니다.

Rate Limiter란?

특정 서비스나 API에 대한 요청 빈도를 제한하여, 과도한 트래픽이나 오남용을 방지합니다. 이를 통해 백엔드 시스템의 과부하를 예방할 수 있습니다.

Bulkhead란?

서비스를 여러 격벽(쓰레드 풀 등)으로 분리하여, 한 부분의 장애가 전체로 확산되는 것을 방지합니다. 예를 들어, 외부 API 호출과 내부 DB 작업을 별도의 Bulkhead로 분리하면, 외부 API 장애가 전체 서비스 장애로 이어지지 않습니다.

참고할 만한 공식 문서 및 레퍼런스

장애에 강한 마이크로서비스를 만들기 위해 Resilience4j를 꼭 활용해보세요. 작은 설정과 코드만으로도 시스템의 안정성을 크게 높일 수 있습니다!