서킷 브레이커와 Resilience4j

Skadi·2024년 8월 7일
0

서킷 브레이커 개요


서킷 브레이커란?

서킷 브레이커는 마이크로서비스 간의 호출 실패를 감지하고 시스템의 전체적인 안정성을 유지하는 패턴입니다. 외부 서비스 호출 실패 시 빠른 실패를 통해 장애를 격리하고, 시스템의 다른 부분에 영향을 주지 않도록 합니다. 서킷 브레이커는 주로 세 가지 상태를 가집니다:

  • 클로즈드 (Closed): 정상 상태로 모든 요청을 통과시킵니다. 실패율이 설정된 임계값을 초과하면 오픈 상태로 전환됩니다.
  • 오픈 (Open): 모든 요청을 즉시 실패로 처리합니다. 일정 시간이 지나면 하프-오픈 상태로 전환됩니다.
  • 하프-오픈 (Half-Open): 제한된 수의 요청을 허용하여 시스템이 정상 상태로 복구되었는지 확인합니다. 성공하면 클로즈드 상태로, 실패하면 다시 오픈 상태로 전환됩니다.

Resilience4j 개요


Resilience4j란?

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

주요 특징

  • 서킷 브레이커 상태: 클로즈드, 오픈, 하프-오픈 상태를 통해 호출 실패를 관리합니다.
  • Fallback: 호출 실패 시 대체 로직을 제공하여 시스템 안정성을 확보합니다.
  • 모니터링: 서킷 브레이커 상태를 모니터링하고 관리할 수 있는 다양한 도구를 제공합니다.

Resilience4j 설정


기본 설정

Resilience4j를 사용하려면 Spring Boot 애플리케이션에 의존성을 추가해야 합니다.

dependencies {
    implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

설정 파일

application.yml 파일에서 Resilience4j의 설정을 할 수 있습니다.

spring:
  application:
    name: sample

server:
  port: 19090

resilience4j:
  circuitbreaker:
    configs:
      default:
        registerHealthIndicator: true
        slidingWindowType: COUNT_BASED
        slidingWindowSize: 5
        minimumNumberOfCalls: 5
        slowCallRateThreshold: 100
        slowCallDurationThreshold: 60000
        failureRateThreshold: 50
        permittedNumberOfCallsInHalfOpenState: 3
        waitDurationInOpenState: 20s

management:
  endpoints:
    web:
      exposure:
        include: prometheus
  prometheus:
    metrics:
      export:
        enabled: true

Fallback 메커니즘


Fallback 설정

Fallback 메서드는 외부 서비스 호출이 실패했을 때 대체 로직을 제공하는 메서드입니다.

@Service
public class MyService {

    @CircuitBreaker(name = "myService", fallbackMethod = "fallbackMethod")
    public String myMethod() {
        return externalService.call();
    }

    public String fallbackMethod(Throwable t) {
        return "Fallback response";
    }
}

Fallback의 장점

  • 시스템의 안정성을 높이고, 장애가 발생해도 사용자에게 일정한 응답을 제공할 수 있습니다.
  • 장애가 다른 서비스에 전파되는 것을 방지합니다.

Resilience4j Dashboard


Dashboard 설정

Resilience4j Dashboard를 사용하여 서킷 브레이커의 상태를 모니터링할 수 있습니다.

dependencies {
    implementation 'io.github.resilience4j:resilience4j-micrometer'
    implementation 'io.micrometer:micrometer-registry-prometheus'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
}

application.yml 파일에서 Prometheus 설정을 추가합니다.

management:
  endpoints:
    web:
      exposure:
        include: prometheus
  prometheus:
    metrics:
      export:
        enabled: true

Dashboard 사용

Prometheus와 Grafana를 사용하여 Resilience4j 서킷 브레이커의 상태를 실시간으로 모니터링할 수 있습니다. Prometheus를 통해 수집된 메트릭을 Grafana 대시보드에서 시각화할 수 있습니다.

실습


이번 실습에서는 유레카를 사용하지 않고, 프로젝트에서는 상품을 조회하는 것을 가정합니다. 상품 아이디 111을 호출하면 에러를 발생시켜 fallbackMethod를 실행하는 것을 확인합니다. 또한 이벤트리스너를 사용하여 서킷 브레이커의 상태를 조회해보겠습니다.

프로젝트 생성 및 코드 작성

Start Spring에서 프로젝트를 생성합니다. 디펜던시는 위 설정을 참고합니다. 아래는 각 파일의 코드 예시입니다.

Product.java

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
    private String id;
    private String title;
}

ProductController.java

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class ProductController {
    private final ProductService productService;

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

ProductService.java

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;

@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");
    }
}

실행

  • http://localhost:19090/products/11을 3번 호출합니다.
  • http://localhost:19090/products/111을 여러 번 호출하면서 서킷 브레이커의 상태가 변경되는 것을 확인합니다.
  • 서킷 브레이커가 Open 상태가 되면 getProductDetails 함수가 호출되지 않고 바로 fallbackGetProductDetails로 호출되는 것을 확인할 수 있습니다.

프로메테우스 데이터 전달

  • http://localhost:19090/actuator/prometheus로 접속하면 인스턴스의 상태 값들을 확인할 수 있습니다.
  • 서킷 브레이커 관련 내용을 확인하여 Prometheus에서 서킷 브레이커의 정보를 수집할 수 있습니다.

이로써 Resilience4j를 이용한 서킷 브레이커 설정 및 활용에 대한 개요와 실습을 마칩니다. 이를 통해 시스템의 안정성과 복원력을 높일 수 있는 방법을 배울 수 있었습니다.

0개의 댓글