Backend/SpringBoot

서킷 브레이커

Dean83 2026. 1. 5. 13:13

MSA 혹은 외부 API를 이용하는 서버의 경우, 외부 API나 DB의 오류로 인해 본 서버에 과부하가 걸리거나 이로인해 오류가 발생 할 수 있다.  즉, 연쇄적인 서비스 오류를 막고자 하는것이 서킷 브레이커 이다.

 

  • 외부 API나 DB가 비정상 동작시 -> 들어온 요청에 바로 결과를 리턴한다 (외부 API 또는 DB에 접근 하지 않음)
    • Fallback을 통해 빠르게 응답을 리턴한다. (예 : DB 접속이 안되면, 캐시에서 조회해보고, 안되면 오류메세지 리턴)
  • 일정 시간이 지난 후, 몇몇 요청에 한해서는 실제 API나 DB에 다시 접속 해 본다. (Half open)
  • 만일 결과가 정상적으로 온다면, 점차 서비스를 정상화 시킨다. 
  • 결과가 정상적으로 오지 않는다면, 다시 대기 후 추후 Half Open을 다시 한다.

 

이를 Springboot 에서 편리하게 이용하기 위해 Resilience4j 를 이용한다.

 

Build.gradle 추가

    implementation "io.github.resilience4j:resilience4j-spring-boot3:2.3.0"

 

application.yaml  추가

  • 서비스에서 @CircuitBraker 어노테이션을 이용하면서 사용한 이름별로 설정이 가능하다. 
resilience4j:
  circuitbreaker:
    configs:
      default:
        registerHealthIndicator: true
        slidingWindowType: COUNT_BASED
        slidingWindowSize: 10
        minimumNumberOfCalls: 5
        failureRateThreshold: 50
        waitDurationInOpenState: 10s
        permittedNumberOfCallsInHalfOpenState: 3
        automaticTransitionFromOpenToHalfOpenEnabled: true

    instances:
      서비스에서_사용할_이름:
        baseConfig: default

 

  • 서비스에서 @CircuitBraker 어노테이션 사용시 이용할 이름을 지정해 줘야 한다.
  • slidingWindowType
    • COUNT_BASED : 최근 요청 횟수 기준
    • TIME_BASED : 최근 시간(초) 기준
  • slidingWindowSize : 최근 몇번 요청을 기준으로 판단 할 것인지
  • minimumNumberOfCalls : 최소 이 횟수 요청 전까지는 서킷브레이커가 열리지 않음.
  • failureRateThreshold : 여기에 설정한 실패율 이상일 경우 서킷브레이커 동작
  • waitDurationInOpenState : 서킷브레이커가 Open된 상태에서 최소 이 시간동안은 완전히 차단함.
  • permittedNomberOfCallsHalfOpenState : Half open 상태에서 테스트용으로 요청할 횟수
  • automaticTransitionFromOpenToHalfOpenEnabled : Open -> Half open 자동전환 여부 (true 설정 필수)

 

서비스에서 사용 예

@Service
@RequiredArgsConstructor
@Slf4j
public class ExternalApiService {

    private final WebClient externalApiWebClient;

    /**
     * 외부 API 호출
     * - CircuitBreaker 적용
     */
    @CircuitBreaker(
            name = "external-api",
            fallbackMethod = "getDataFallback"
    )
    public ExternalDto getExternalData(Long id) {

        return externalApiWebClient.get()
                .uri("/v1/data/{id}", id)
                .retrieve()
                .bodyToMono(ExternalResponse.class)

                // WebClient 자체 타임아웃
                .timeout(Duration.ofSeconds(2))

                // 외부 → 내부 변환
                .map(ExternalDto::from)

                // MVC 환경
                .block();
    }

    /**
     * CircuitBreaker OPEN 또는 예외 발생 시 호출되는 fallback
     */
    private ExternalDto getDataFallback(Long id, Throwable ex) {
        log.warn("CircuitBreaker fallback 실행 (id={})", id, ex);

        // 대체 응답 (빈 객체 / 캐시 / 기본값)
        return ExternalDto.empty();
    }
}
  • name 에 사용한 external-api 는 application.yaml 설정에 포함되어 있어야 한다. (instances  하위)
  • fallBack 설정을 해주어야 한다. fallBackMethod 에서 지정한 이름과 실제 메서드 이름이 같아야 한다.