분산 처리 환경

스프링 분산 처리 환경 9: Resilience4j 서킷 브레이커 적용

ohji52 2025. 11. 21. 20:41

서킷 브레이커(Circuit Breaker) 

마이크로서비스 아키텍처(MSA)에서 특정 서비스에 장애가 발생했을 때 아무런 조치가 없다면 게이트웨이는 계속 요청을 보내고, 스레드는 대기하다 고갈되며 시스템 전체가 멈추는 연쇄 장애로 이어진다.

서킷 브레이커(Circuit Breaker) 패턴을 도입해 문제가 생긴 서비스로 가는 경로를 차단 후 대체 경로로 안내해 시스템을 보호한다.

 

의존성 추가(build.gradle)

dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
    implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j'
    implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer'
}

 

ResilienceConfig

package com.example.distributed.config;

import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

@Configuration
public class ResilienceConfig {

    @Bean
    public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer() {
        return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
                .circuitBreakerConfig(CircuitBreakerConfig.custom()
                        .slidingWindowSize(10)
                        .failureRateThreshold(50)
                        .waitDurationInOpenState(Duration.ofSeconds(10))
                        .build())
                .timeLimiterConfig(TimeLimiterConfig.custom()
                        .timeoutDuration(Duration.ofSeconds(4))
                        .build())
                .build());
    }
}

서킷 브레이커 정책 설정

어떤 상황에서 경로를 차단할지 구체적인 규칙을 정의합니다.

  • Sliding Window (10): 최근 10개의 요청을 기준으로 장애율을 계산
  • Failure Rate Threshold (50%): 요청 중 50% 이상이 실패하면 서킷을 열어(Open) 요청을 즉시 차단
  • Wait Duration (10초): 서킷이 열린 후 10초간 대기하며 서비스를 보호한 뒤, 다시 연결을 시도(Half-open)
  • Time Limiter (4초): 응답이 4초 이상 걸리면 강제로 타임아웃 처리하여 스레드 점유를 방지

FallbackController

package com.example.distributed.controller;
// ... (필요한 import 생략)

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/fallback")
public class FallbackController {

    @GetMapping("/externalService")
    public ResponseEntity<Map<String, Object>> externalServiceFallback() {
        Map<String, Object> response = new HashMap<>();
        response.put("success", false);
        response.put("message", "현재 시스템 부하로 인해 요청을 처리할 수 없습니다. 잠시 후 다시 시도해 주세요.");
        return new ResponseEntity<>(response, HttpStatus.SERVICE_UNAVAILABLE);
    }
}

대체 경로 제공

서비스가 차단되었을 때 클라이언트가 받게 될 '안전한 응답'을 정의

  • 단순한 에러 페이지 대신, "현재 시스템 부하로 인해 요청을 처리할 수 없습니다"와 같은 안내 메시지를 담아 503 Service Unavailable 상태로 반환. 이는 클라이언트 앱이 상황에 맞게 UI를 대응할 수 있도록 함

테스트

@Test
@DisplayName("백엔드_서비스_다운_시_서킷_브레이커가_Fallback_경로로_전달해야_한다")
void 백엔드_서비스_다운_시_서킷_브레이커가_Fallback_경로로_전달해야_한다() {
    webClient.get().uri(PROTECTED_SERVICE_URI)
            .header("Authorization", "Bearer " + VALID_TOKEN)
            .exchange()
            .expectStatus().is5xxServerError()
            .expectBody()
            .jsonPath("$.message").isEqualTo("현재 시스템 부하로 인해 요청을 처리할 수 없습니다. 잠시 후 다시 시도해 주세요.");
}

 

 

  1. 시스템 전체의 연쇄 장애 방지
    • 장애가 발생한 서비스로의 요청을 입구(Gateway)에서 즉시 차단
    •  특정 서비스가 죽어도 다른 서비스(로그인, 주문 등)는 정상적으로 작동할 수 있는 '장애 격리'가 이루어짐
  2. 자원 보호 및 빠른 회복 (Fail-Fast)
    • 응답이 오지 않는 서비스를 무한정 기다리지 않음
    • 서버의 스레드나 메모리가 대기 중인 요청들로 인해 고갈되는 것을 막아, 장애 서비스가 복구되었을 때 시스템이 더 빠르게 정상 상태로 돌아올 수 있음
  3. 사용자 경험(UX) 유지
    •  알 수 없는 에러나 무한 로딩 대신 명확한 안내를 제공
    • 사용자는 "시스템 점검 중"임을 인지하고 잠시 후 다시 시도할 수 있으며, 클라이언트 앱은 무한 로딩 스피너에 갇히지 않음.
  4. 인프라 안정성 향상
    •  부하가 걸린 서비스에 계속해서 '재시도 폭탄'을 던지는 것을 방지
    • 시적인 과부하 상태에서 서킷 브레이커가 요청을 차단해 주는 동안, 대상 서비스는 스스로 회복할 수 있는 최소한의 시간을 벌 수 있음

 

 

feat: Gateway 안정성 확보를 위한 Resilience4j 서킷 브레이커 및 Fallback 구현 by 0hj1hyeon · Pull Request #9 ·

WebFlux 기반 resilience4j를 사용 Fallback 처리: 장애 발생 시 FallbackController로 포워딩 503 상태와 메시지 전달

github.com