서킷 브레이커 (Resilience4j)

ayboori·2024년 8월 5일
0

MSA

목록 보기
5/8

서킷 브레이커란?

참고

  • 서킷 브레이커는 마이크로서비스 간의 호출 실패를 감지하고 시스템의 전체적인 안정성을 유지하는 패턴
  • 외부 서비스 호출 실패 시 빠른 실패를 통해 장애를 격리하고, 시스템의 다른 부분에 영향을 주지 않도록 합니다.
  • 이상 회복 시 자동으로 다시 연동
  • 상태 변화: 클로즈드 -> 오픈 -> 하프-오픈

Resilience4j란?

  • Resilience4j는 서킷 브레이커 라이브러리로, 서비스 간의 호출 실패를 감지하고 시스템의 안정성을 유지합니다.
  • 다양한 서킷 브레이커 기능을 제공하며, 장애 격리 및 빠른 실패를 통해 복원력을 높입니다.
  • 함수형 프로그래밍으로 설계가 되어서 functional interface, lambda, method reference 등을 활용하여 구현할 수 있습니다.

Resilience4j의 주요 특징

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

Resilience4j 설정

기본 설정

resilience4j 의존성은 spring starter에서 추가하여 사용하지 않을 것입니다.
”io.github.resilience4j:resilience4j-spring-boot3:2.2.0” 를 사용합니다.
”boot3” 임을 주의 하세요. > gpt에서 오류 찾기 힘들 수 있다.

  • Resilience4j를 사용하려면 Spring Boot 애플리케이션에 의존성을 추가해야 합니다.
  • build.gradle 파일 예시:
    
    dependencies {
        implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
    	  implementation 'org.springframework.boot:spring-boot-starter-aop'
    }
    

Resilience4j 설정 파일

  • Resilience4j의 설정은 application.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초로 설정
            

설정은 프로젝트에 따라 미리 되어있을 수도 있고, 필요한 설정만 추가하는 방식으로 수정할 수도 있다. default로 사용한 뒤에 그때그때 추가하는 것이 좋다.

Fallback 메커니즘

Fallback 설정

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

  • 예시 코드:

    @Service
    public class MyService {
    
    	//fallbackMethod : 실패 시 실행할 대체 메소드
        @CircuitBreaker(name = "myService", fallbackMethod = "fallbackMethod")
        public String myMethod() {
            // 외부 서비스 호출
            return externalService.call();
        }
    
        public String fallbackMethod(Throwable t) {
            return "Fallback response";
        }
    }
    
  • Fallback 메서드에 작성하면 좋을 내용
    fallback 발생 시 alert (sns 등을 통해 개발자에게 알림)
    사용자에게 대체 로직이나 현재 반응이 불가하다는 것을 알림
    에러가 발생해도 에러처럼 보이지 않게 하자!

Fallback의 장점

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

Resilience4j Dashboard

Resilience4j Dashboard 설정

  • Resilience4j Dashboard를 사용하여 서킷 브레이커의 상태를 모니터링할 수 있습니다.
  • build.gradle 파일 예시:
    
    dependencies {
        implementation 'io.github.resilience4j:resilience4j-micrometer'
        implementation 'io.micrometer:micrometer-registry-prometheus'
        implementation 'org.springframework.boot:spring-boot-starter-actuator'
    }
    
  • 설정은 application.yml 파일에서 설정할 수 있습니다.
  • 예시 코드:
    management:
      endpoints:
        web:
          exposure:
            include: prometheus #가 수집한 데이터를 Grafana에서 상태 모니터링
      prometheus:
        metrics:
          export:
            enabled: true
  • http://${hostname}:${port}/actuator/prometheus 에 접속하여 서킷브레이커 항목을 확인 가능합니다.

Resilience4j Dashboard 사용

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

Resilience4j와 Spring Cloud 연동

Spring Cloud와의 통합

  • Resilience4j는 Spring Cloud Netflix 패키지의 일부로, Eureka와 Ribbon 등 다른 Spring Cloud 구성 요소와 쉽게 통합할 수 있습니다.
  • Spring Cloud의 서비스 디스커버리와 로드 밸런싱을 활용하여 더욱 안정적인 마이크로서비스 아키텍처를 구축할 수 있습니다.

spring:
  application:
    name: my-service
  cloud:
    circuitbreaker:
      resilience4j:
        enabled: true

실습

15'40 부터

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

프로젝트 생성 및 코드 작성

❗ 주의 starter에서 resilience4j 디펜던시를 추가하지 않습니다.
starter 에서 추가하면 “org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j”가 추가됩니다.
이는 추상화 계층을 통해 Resilience4j를 사용하는데 (구현체가 없고 인터페이스만 있어서 우리가 생성해야 함) 우리는 직접 resilience4j를 사용하기 위해 build.gradle에 “io.github.resilience4j:resilience4j-spring-boot3:2.2.0”를 추가하겠습니다.

  • build.gradle의 dependencies에 다음을 추가
    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'
    }
    
    tasks.named('test'){
    		useJUnitPlatform()
    }

github 문서의 document를 바탕으로 버전에 맞는 의존성을 추가해야 한다.

  • java/com/spring_cloud/resilience4j/sample/products/Product.java
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Product {
    
        private String id;
        private String title;
    
    }
  • java/com/spring_cloud/resilience4j/sample/products/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);
        }
    }
  • java/com/spring_cloud/resilience4j/sample/products/ProductService.java

private final CircuitBreakerRegistry circuitBreakerRegistry;를 통해 서킷 브레이커 사용

    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;
    
    	// 서킷 브레이커 설정, 실패 시 수행할 메소드 지정
        @CircuitBreaker(name = "productService", fallbackMethod = "fallbackGetProductDetails")
        public Product getProductDetails(String productId) {
            log.info("###Fetching product details for productId: {}", productId);
            
            // 아이디가 111이면 오류 발생 가정 > 로그 작성, 에러 던짐
            if ("111".equals(productId)) {
                log.warn("###Received empty body for productId: {}", productId);
                throw new RuntimeException("Empty response body");
            }
            return new Product(
                productId,
                "Sample Product"
            ); // 성공 시 값 리턴
        }
    
    // getProductDetails 실패 시 수행할 메소드
        public Product fallbackGetProductDetails(String productId, Throwable t) {
            log.error("####Fallback triggered for productId: {} due to: {}", productId, t.getMessage());
            return new Product(
                productId,
                "Fallback Product"
            ); // 실패 시 값 리턴, 일정 시간 동안은 이 메소드가 수행됨
        }
    }
    
       	// 상태 변경 체크 용도 메소드
        @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)); // 오류 발생 이벤트 리스너
        }
        
  • 이벤트
이벤트 설명 표
+---------------------------+-------------------------------------------------+--------------------------------------------+
| 이벤트                      | 설명                                             | 로그 출력                                    |
+---------------------------+-------------------------------------------------+--------------------------------------------+
| 상태 전환 (Closed -> Open)   | 연속된 실패로 인해 서킷 브레이커가 오픈 상태로 전환되면 발생  | CircuitBreaker State Transition: ...       |
| 실패율 초과                  | 설정된 실패율 임계치를 초과하면 발생                     | CircuitBreaker Failure Rate Exceeded: ...  |
| 호출 차단                    | 서킷 브레이커가 오픈 상태일 때 호출이 차단되면 발생         | CircuitBreaker Call Not Permitted: ...     |
        // | 오류 발생                    | 서킷 브레이커 내부에서 호출이 실패하면 발생               | CircuitBreaker Error: ...                  |
+---------------------------+-------------------------------------------------+--------------------------------------------+
    
+------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+
| 이벤트                                    | 설명                                        | 로그 출력                                                         |
+------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+
| 메서드 호출                                | 제품 정보를 얻기 위해 메서드를 호출                | ###Fetching product details for productId: ...                  |
| (성공 시) 서킷 브레이커 내부에서 호출 성공        | 메서드 호출이 성공하여 정상적인 응답을 반환          |                                                                 |
| (실패 시) 서킷 브레이커 내부에서 호출 실패        | 메서드 호출이 실패하여 예외가 발생                 | #######CircuitBreaker Error: ...                                |
| (실패 시) 실패 횟수 증가                      | 서킷 브레이커가 실패 횟수를 증가시킴               |                                                                 |
| (실패율 초과 시) 실패율 초과                   | 설정된 실패율 임계치를 초과하면 발생               | #######CircuitBreaker Failure Rate Exceeded: ...                |
| (실패율 초과 시) 상태 전환 (Closed -> Open)   | 연속된 실패로 인해 서킷 브레이커가 오픈 상태로 전환됨   | #######CircuitBreaker State Transition: Closed -> Open at ...  |
| (오픈 상태 시) 호출 차단                      | 서킷 브레이커가 오픈 상태일 때 호출이 차단됨         | #######CircuitBreaker Call Not Permitted: ...                   |
| (오픈 상태 시) 폴백 메서드 호출                 | 메서드 호출이 차단될 경우 폴백 메서드 호출          | ####Fallback triggered for productId: ... due to: ...           |
+------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+
  • resources/application.yml
    spring:
      application:
        name: sample
    
    server:
      port: 19090
    
    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초로 설정
    
    management:
      endpoints:
        web:
          exposure:
            include: prometheus
      prometheus:
        metrics:
          export:
            enabled: true

5.7.2 Run

5.7.3 프로메테우스 데이터 전달

  • http://localhost:19090/actuator/prometheus로 접속하면 인스턴스의 상태 값들이 나옵니다.
  • 아래로 내려가다보면 서킷브레이커 관련 내용을 확인할 수 있습니다. 이를통해 프로메테우스에서 서킷브레이커의 정보를 수집할 수 있습니다.
profile
프로 개발자가 되기 위해 뚜벅뚜벅.. 뚜벅초

0개의 댓글