[MSA알아보기] 서킷브레이커(Spring Cloud Circuit Breaker)

차차의 개발일기·2024년 8월 6일
0

msa

목록 보기
5/7
post-thumbnail

이번 블로그에서는 스프링 클라우드 서킷브레이커를 중심으로, 그 개념과 사용법, 실제 적용 예제까지 상세히 알아보겠습니다.
자세한 내용은 GIT-circuit-breaker 브랜치에서 알아보실 수 있습니다.


1. 서킷브레이커란?

서킷브레이커 패턴은 네트워크 호출에서 발생하는 잠재적인 장애를 격리하고, 전체 시스템으로의 영향을 최소화하기 위해 고안된 패턴입니다.

1.1 주요 동작 원리

  • Closed 상태: 요청이 정상적으로 처리됩니다. 서킷이 닫힌 상태입니다.
  • Open 상태: 일정 시간 동안 요청이 차단됩니다. 서킷이 열린 상태로, 시스템이 복구될 시간을 줍니다.
  • Half-Open 상태: 일부 요청을 허용하여 시스템이 정상적으로 작동하는지 확인합니다. 상태가 다시 Closed로 돌아갈지 Open 상태를 유지할지 결정합니다.

1.2 서킷브레이커 장점

  • 시스템 회복력 향상 : 장애 발생 시 빠르게 감지하고 대응할 수 있습니다.
  • 장애 확산 방지 : 장애가 다른 서비스로 확산되는 것을 방지합니다.
  • 성능 개선 : 불필요한 대기 시간과 리소스 낭비를 줄입니다.

2. 스프링 클라우드 서킷브레이커

스프링 클라우드 서킷브레이커는 다양한 서킷브레이커 라이브러리를 통합하고, 이를 쉽게 설정하고 사용할 수 있는 기능을 제공합니다. 대표적으로 Resilience4j와 Hystrix를 지원합니다.

더이상의 기능추가가 없는 Hystrix가 아닌 Resilience4j에 대해 정리하고 실습을 진행하곘습니다.

2.1 주요 기능과 특징

  • 다양한 서킷브레이커 라이브러리 통합
  • 간편한 설정 및 사용법
  • 유연한 커스터마이징 옵션

3. Resilience4j

3.1 Resilience4j란?

  • Resilience4j는 서킷 브레이커 라이브러리로, 서비스 간의 호출 실패를 감지하고 시스템의 안정성을 유지합니다.
  • 다양한 서킷 브레이커 기능을 제공하며, 장애 격리 및 빠른 실패를 통해 복원력을 높입니다.

3.2 주요 특징

  • 서킷 브레이커 상태: 클로즈드, 오픈, 하프-오픈 상태를 통해 호출 실패를 관리
    • 클로즈드(Closed):
      • 기본 상태로, 모든 요청을 통과시킵니다.
      • 이 상태에서 호출이 실패하면 실패 카운터가 증가합니다.
      • 실패율이 설정된 임계값(예: 50%)을 초과하면 서킷 브레이커가 오픈 상태로 전환됩니다.
      • 예시: 최근 5번의 호출 중 3번이 실패하여 실패율이 60%에 도달하면 오픈 상태로 전환됩니다.
    • 오픈(Open):
      • 서킷 브레이커가 오픈 상태로 전환되면 모든 요청을 즉시 실패로 처리합니다.
      • 이 상태에서 요청이 실패하지 않고 바로 에러 응답을 반환합니다.
      • 설정된 대기 시간이 지난 후, 서킷 브레이커는 하프-오픈 상태로 전환됩니다.
      • 예시: 서킷 브레이커가 오픈 상태로 전환되고 20초 동안 모든 요청이 차단됩니다.
    • 하프-오픈(Half-Open):
      • 오픈 상태에서 대기 시간이 지나면 서킷 브레이커는 하프-오픈 상태로 전환됩니다.
      • 하프-오픈 상태에서는 제한된 수의 요청을 허용하여 시스템이 정상 상태로 복구되었는지 확인합니다.
      • 요청이 성공하면 서킷 브레이커는 클로즈드 상태로 전환됩니다.
      • 요청이 다시 실패하면 서킷 브레이커는 다시 오픈 상태로 전환됩니다.
      • 예시: 하프-오픈 상태에서 3개의 요청을 허용하고, 모두 성공하면 클로즈드 상태로 전환됩니다. 만약 하나라도 실패하면 다시 오픈 상태로 전환됩니다.
  • Fallback: 호출 실패 시 대체 로직을 제공하여 시스템 안정성 확보
  • 모니터링: 서킷 브레이커 상태를 모니터링하고 관리할 수 있는 다양한 도구 제공

3.3 구현

앞서 MSA에서 진행중인 프로젝트중 product에서 추가 구현을 진행하겠습니다.
이전 글이 궁금하시다면 [MSA알아보기] 클라이언트 사이드 로드 밸런싱(FeignClient와 Ribbon)를 확인해주시면 감사하겠습니다.

3.3.1 의존성 구현

dependencies {
    implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
	implementation 'org.springframework.boot:spring-boot-starter-aop'
}

위와 같이 resilence4j와 apo를 추가해주시면 됩니다.

3.3.2 yml 설정

resilience4j:
  circuitbreaker:
    configs:
      default:  # 기본 구성 이름
        registerHealthIndicator: true  # 애플리케이션의 헬스 체크에 서킷 브레이커 상태를 추가하여 모니터링 가능
        # 서킷 브레이커가 동작할 때 사용할 슬라이딩 윈도우의 타입을 설정
        # COUNT_BASED: 마지막 N번의 호출 결과를 기반으로 상태를 결정
        # TIME_BASED: 마지막 N초 동안의 호출 결과를 기반으로 상태를 결정
        slidingWindowType: COUNT_BASED  # 슬라이딩 윈도우의 타입을 호출 수 기반(COUNT_BASED)으로 설정
        # 슬라이딩 윈도우의 크기를 설정
        # COUNT_BASED일 경우: 최근 N번의 호출을 저장
        # TIME_BASED일 경우: 최근 N초 동안의 호출을 저장
        slidingWindowSize: 5  # 슬라이딩 윈도우의 크기를 5번의 호출로 설정
        minimumNumberOfCalls: 5  # 서킷 브레이커가 동작하기 위해 필요한 최소한의 호출 수를 5로 설정
        slowCallRateThreshold: 100  # 느린 호출의 비율이 이 임계값(100%)을 초과하면 서킷 브레이커가 동작
        slowCallDurationThreshold: 60000  # 느린 호출의 기준 시간(밀리초)으로, 60초 이상 걸리면 느린 호출로 간주
        failureRateThreshold: 50  # 실패율이 이 임계값(50%)을 초과하면 서킷 브레이커가 동작
        permittedNumberOfCallsInHalfOpenState: 3  # 서킷 브레이커가 Half-open 상태에서 허용하는 최대 호출 수를 3으로 설정
        # 서킷 브레이커가 Open 상태에서 Half-open 상태로 전환되기 전에 기다리는 시간
        waitDurationInOpenState: 20s  # Open 상태에서 Half-open 상태로 전환되기 전에 대기하는 시간을 20초로 설정

3.3.3 엔티티, 서비스, 컨트롤러 구현

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {

    private String id;
    private String title;

}
@RestController
@RequiredArgsConstructor
public class ProductController {

    private final ProductService productService;


    @GetMapping("/product/{id}")
    public Product getProduct(@PathVariable("id") String id) {
        return productService.getProductDetails(id);
    }
}
@Service
@RequiredArgsConstructor
public class ProductService {

    private final Logger log = LoggerFactory.getLogger(getClass());
    private final CircuitBreakerRegistry circuitBreakerRegistry;

    @PostConstruct
    public void registerEventListener() {
        circuitBreakerRegistry.circuitBreaker("productService").getEventPublisher()
            .onStateTransition(event -> log.info("#######CircuitBreaker State Transition: {}", event)) // 상태 전환 이벤트 리스너
            .onFailureRateExceeded(event -> log.info("#######CircuitBreaker Failure Rate Exceeded: {}", event)) // 실패율 초과 이벤트 리스너
            .onCallNotPermitted(event -> log.info("#######CircuitBreaker Call Not Permitted: {}", event)) // 호출 차단 이벤트 리스너
            .onError(event -> log.info("#######CircuitBreaker Error: {}", event)); // 오류 발생 이벤트 리스너
    }


    @CircuitBreaker(name = "productService", fallbackMethod = "fallbackGetProductDetails")
    public Product getProductDetails(String productId) {
        log.info("###Fetching product details for productId: {}", productId);
        if ("111".equals(productId)) {
            log.warn("###Received empty body for productId: {}", productId);
            throw new RuntimeException("Empty response body");
        }
        return new Product(
            productId,
            "Sample Product"
        );
    }

    public Product fallbackGetProductDetails(String productId, Throwable t) {
        log.error("####Fallback triggered for productId: {} due to: {}", productId, t.getMessage());
        return new Product(
            productId,
            "Fallback Product"
        );
    }
}

3.4 실행

  1. 컨트롤러를 실행한 후 http://localhost:{개인설정 포트번호}/products/11를 3번 호출합니다.
  2. http://localhost:19090/products/111를 여러번 호출합니다.
  3. 상태를 확인합니다.

여기서 확인 가능한 것은
1. 에러가 지속적으로 발생이 되면 CLOSED -> OPEN 상태로 변경이 됩니다.
2. OPNE -> HALFOPEN 상태가 되기 위해 yml에서 설정한 20초정도가 걸리는것을 볼 수 있습니다3.
3. HALF_OPEN 상태에서는 여러번 호출을 하더라 3번으로 제한됩니다.
4. 에러가 없다고 판단되면 CLOSED로 변경되는 것을 볼 수 있습니다.
5. Open 상태가 되면
getProductDetails 함수를 타지않고 바로 fallbackGetProductDetails_ 로 호출 되는것을 확인 할 수 있습니다.

이상으로 구현을 마무리 하겠습니다.


🔗Github Repository 링크

https://github.com/junghojune/MSA-Practice

profile
1년차 개발자 차차입니다.

0개의 댓글