[MSA] Resilience4j 개념 복습

Kim Hyen Su·2024년 4월 29일

MSA

목록 보기
18/18
post-thumbnail

Resilience4j 공식 문서

이전 포스팅

1. 개요

MSA 프로젝트를 진행하면서 일부 서비스 장애가 시스템 전체로 전파되는 문제를 해결하기 위한 장치의 필요성을 느꼈습니다.

이를 해결하기 위해 "CircuitBreaker 패턴"이라는 패턴이 존재합니다. 스프링 프레임워크에서 제공하는 장애 사항에 대한 솔루션으로 Neflix Hysterix와 Resilience4j가 있습니다.


2. Resilience4j

Resilience4j는 Neflix의 Hystrix에 영감을 받아 만들어진 Java 전용의 fault tolerance를 구현한 라이브러리입니다.

fault tolerance란 일부 장애가 일어나더라도 전체로 확산되는 것을 막고 시스템이 정상 동작하도록 해주는 것을 말합니다.

💡 주의
그렇다고, fault tolerance와 resilience 라는 개념이 동일한 것은 아닙니다.

  • fault tolerance : 일부 서비스에 장애가 발생해도 시스템은 정상적으로 기능하는 능력을 말합니다.
  • resilience : 시스템에 발생한 장애로부터 회복하고 원래 상태로 되돌아갈 수 있는 능력을 말합니다.

Resilience4j의 핵심 기능으로는 다음과 같이 있습니다.

  • CircuitBreaker : 실패한 실행에 대해서 또 다른 시도가 들어올 때 바로 실패 처리합니다.

  • Bulkhead : 동시에 실행할 수를 제한합니다.

  • RateLimiter : 일정 시간동안 들어올 수 있는 요청을 제한합니다.

  • Retry : 실패한 실행을 짧은 지연을 가진 후 재시도합니다.

  • TimeLimiter : 실행되는 시간을 특정 시간을 넘기지 않도록 제한합니다.

  • Cache : 성공적인 실행을 캐시합니다. 다음번에 동일한 요청으로 들어오면 바로 반환해줍니다.

  • Fallback : 실행 실패 시 대신 실행되는 프로세스를 말합니다.

Resilience4j 2는 Java 17버전 이상부터 지원합니다.


3. Netflix Hystrix와의 비교

그렇다면 위에서 언급한 Netflix Hystrix와의 차이점은 어떤것들이 있을까요?

  • Resilience4J는 Java 8에서 함수형 프로그래밍을 위한 모듈인 vavr만을 의존하는 반면, Hystrix는 Guava, Apache Commons, Archaius와 같은 큰 모듈들을 의존합니다.

  • Resilience4J는 Circuit Breaker, Rate Limiter, Retry, Bulkhead를 사용자가 원하는 순서대로 decorator pattern처럼 감싸는 형태로 사용할 수 있습니다.

  • Hystrix에서는 외부 시스템으로의 호출을 HystrixCommand로 래핑해야 하지만, Resilience4J는 함수형 인터페이스, 람다 표현식, 또는 메서드 참조를 Circuit Breaker, Rate Limiter, Retry, Bulkhead로 향상시키는 데 사용할 수 있는 고차원 함수(decorators)를 제공합니다.

참고

Netflix Hystrix is now officially in maintenance mode, with the following
expectations to the greater community: Netflix will no longer actively review
issues, merge pull-requests, and release new versions of Hystrix.

Hystrix 공식 Github 문서에서도 Hystrix는 더 이상 관리되지 않고 유지보수 모드에 있기 때문에, 새로운 프로젝트에는 Resilience4J를 권장합니다.

❓ 고차원 함수(Decorators)

함수를 매개변수로 받고, 다른 함수를 반환하는 함수를 의미합니다.

❓ Decorator pattern

디자인 패턴 중 하나로, 기능을 동적으로 추가하거나 확장 시에 사용하는 패턴을 말합니다.
상속을 사용하는 대신에 집합(합성)관계를 통해서 기능을 확장합니다.
집합(합성)관계는 한 객체(O1)에 대한 참조를 다른 객체(O2) 안에서 하여,
해당 객체(O1)에 대한 기능을 확장는것을 말합니다.


4. Resilience4j 제공하는 모듈(기능)

CircuitBreaker

직역하면 회로 차단기이며, 일반 가정집에서 일명 두꺼비집 이라고 불리는 누전 차단기와 동일하게 동작합니다. 즉, 서비스 간의 장애 전파를 막기위해 OPEN/CLOSE 한다고 이해하면 됩니다.

CircuitBreaker는 일부 서비스 장애에도 서비스를 안정적으로 제공하며, 많은 부하를 받은 일부 장애 서비스가 회복할 수 있도록 트래픽을 차단하여 도움을 제공합니다.

해당 모듈은 Resilience4j의 핵심 모듈 중 하나로, fault tolerance를 구현하기 위한 모듈입니다.

CircuitBreaker는 내부적으로 슬라이딩 윈도우 패턴을 통해서 요청을 집계하고, 임계치 이상일 경우 Circuit을 OPEN하는 식으로 동작합니다.

출처 : https://oliveyoung.tech/blog/2023-08-31/circuitbreaker-inventory-squad/
그림

기본 상태

  1. CLOSED (닫힘 상태)
  • 정상 상태
  • CircuitBreaker에서 요청을 집계 및 모니터링합니다.
  • 실패 임계치(failureRateThreshold or slowCallRateThreshold) 도달시 CLOSED 에서 OPEN 으로 상태 변경
  1. OPEN(열림 상태)
  • 장애 상태
  • Open 상태에서 일정 시간(waitDurationInOpenState) 소요시 HALF_OPEN 으로 상태 변경
  1. HALF-OPEN(반열림 상태)
  • OPEN 이후 일정 요청 횟수 or 시간이 지난 상태
  • Half Open 상태에서의 요청 수행은 다음과 같습니다.
    a. 지정한 횟수 (permittedNumberOfCallsInHalfOpenState 횟수)만큼 수행 후 성공 시 Half Open 상태에서 CLOSED 상태로 변경
    b. 지정한 횟수 (permittedNumberOfCallsInHalfOpenState 횟수만큼) 수행 후 실패 시 Half Open 상태에서 OPEN 상태로 변경

CircuitBreaker는 " 슬라이딩 윈도우 " 를 사용하여 호출 결과를 저장하고 집계합니다. 이 때, 방식은 갯수 기반 슬라이딩 원되우와 시간 기반 슬라이딩 윈도우 중에서 하나를 선택할 수 있습니다.

  • 갯수 기반 슬라이딩 : N개의 호출의 결과 집계
  • 시간 기반 슬라이딩 : N초 동안의 호출 결과를 집계

문제 상황 가정

  1. 문제 발생(예외 또는 응답 지연이 기준치 이상 발생) (Close -> Open)
  2. 대기(시스템이 회복할 수 있는 시간동안 대기)(Open)
  3. Half-Open 전환(시스템이 회복할 수 있는 시간이 지난 후 Half-Open으로 변경)(Open -> Half-Open)
  4. 점검(Half-Open에서 시스템이 회복됐는지 점검하며, 이는 Close 상태처럼 트래픽을 흘려봅니다.)
    5-1. 회복(시스템이 회복됐다고 판단되면 CLOSE 상태로 변경하여 트래픽 정상 유입)(Half-Open ->Close)
    5-2 시스템이 회복되지 않았다고 판단되면 OPEN 상태로 변경하여 트래픽 차단)(Half-Open -> Open)

HALF_OPEN 상태가 왜 필요할까?

1번 과정과 5번과정의 조건을 구분하여 설정해야 조금 더 유연한 처리가 가능하기 때문입니다.
만약, 1번과 5-2번 동작이 같은 설정으로 동작하게 될 경우, 제한적으로 5-2번을 동작하는 경우, 빠르게 차단해줘야 하지만, 기존에 1번의 설정과 함께 사용되므로, 5-2번만의 동작에 대한 독립적인 설정이 불가능하여, 처리가 어려워지기 때문입니다.

CircuitBreaker에서 장애 판단 기준

  1. slow call : 설정값 보다 오래 걸린 요청
  2. failure call : 실패한 요청(예외)

실패율 및 느린 호출 속도 임계값

실패율이 임계치보다 크거나 같을 경우 CircuitBreaker의 상태가 CLOSED에서 OPEN으로 변경됩니다. 예를 들어 전체 요청의 50% 이상이 실패한 경우입니다.(설정 : 50)

느린 호출율이 임계치보다 크거나 같을 경우 CircuitBreaker의 상태가 CLOSED에서 OPEN으로 변경됩니다. 예를 들어 전체 요청의 50% 이상이 5초 이상이 걸린 경우입니다.

실패율과 느린 호출율은 최소 횟수가 기록된 경우에만 계산이 가능합니다. 예를 들어, 필요 최소 횟수가 10개인 경우 실패율 계산을 위해서 최소 10개의 요청을 받아야 집계가 가능합니다. 따라서, 9개 중 9개가 모두 fail되더라도 CircuitBreaker는 Trip 되지 않습니다.

CircuitBreaker가 OPEN 상태인 경우, 모든 호출을 거부합니다. 설정한 대기 시간이 경과한 뒤 CircuitBreaker는 HALF_OPEN 상태로 변경되고 서버를 여전히 사용할 수 없거나 다시 사용할 수 있게 됐는지 확인하기 위해 구성 가능한 호출 수를 허용 하게 됩니다.

허용된 모든 호출이 성공될 때까지 추가 호출은 거부됩니다. 실패율 또는 느린 호출율이 구성된 임계값보다 크거나 같으면 상태가 다시 OPEN으로 변경됩니다. 낮을 경우 CLOSED로 변경됩니다.

CircuitBreaker는 OPEN 상태인 경우, CallNotPermittedException을 던져 호출에 응답합니다.

Resilience4j는 Thread-safe와 원자성 보장을 제공하는 ConcurrentHashMap 기반의 인메모리 CircuitBreakerRegistry를 제공해줍니다. 해당 객체에서 CircuitBreaker 관련 설정이 관리되며, CircuitBreaker 객체를 얻어올 수 있습니다.

특수 상태

CircuitBreaker는 DISABLED(항상 엑세스 허용)과 FORCED_OPEN(항상 엑세스 거절) 이라는 특수 상태를 지원합니다. 해당 상태에서는 CircuitBreaker 이벤트가 생성되지 않으며, 메트릭도 기록되지 않습니다. 이러한 상태를 종료하는 유일한 방법은 상태 전환을 트리거하거나 CircuitBreaker를 재설정하는 것입니다.

Create a CircuitBreakerRegistry

Resilience4j는 thread-safe 와 원자성을 보장해주는 ConcurrentHashMap 기반 인 메모리 CircuiBreakerRegistry를 함께 제공합니다.
CircuitBreakerRegistry를 통해서 CircuitBreaker 인스턴스들을 생성 및 조회가 가능합니다. 모든 인스턴스들을 위한 글로벌 default CircuitBreakerConfig를 사용하는 CircuitBreakerRegistry는 다음과 같이 생성 가능합니다.

CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();

CircuitBreaker 설정 생성

개발자가 자체적으로 커스텀한 글로벌 CircuitBreakerConfig를 제공하는 것도 가능합니다.

각 설정별 기본값

-Resilience4j 공식 문서 CircuitBreaker 설정

일반적으로는 config 클래스에서 builder를 통해 설정을 생성 및 적용하는 것이 가능합니다.

// Create a custom configuration for a CircuitBreaker
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
  .failureRateThreshold(50)
  .slowCallRateThreshold(50)
  .waitDurationInOpenState(Duration.ofMillis(1000))
  .slowCallDurationThreshold(Duration.ofSeconds(2))
  .permittedNumberOfCallsInHalfOpenState(3)
  .minimumNumberOfCalls(10)
  .slidingWindowType(SlidingWindowType.TIME_BASED)
  .slidingWindowSize(5)
  .recordException(e -> INTERNAL_SERVER_ERROR
                 .equals(getResponse().getStatus()))
  .recordExceptions(IOException.class, TimeoutException.class)
  .ignoreExceptions(BusinessException.class, OtherBusinessException.class)
  .build();

// Create a CircuitBreakerRegistry with a custom global configuration
CircuitBreakerRegistry circuitBreakerRegistry = 
  CircuitBreakerRegistry.of(circuitBreakerConfig);

// Get or create a CircuitBreaker from the CircuitBreakerRegistry 
// with the global default configuration
CircuitBreaker circuitBreakerWithDefaultConfig = 
  circuitBreakerRegistry.circuitBreaker("name1");

// Get or create a CircuitBreaker from the CircuitBreakerRegistry 
// with a custom configuration
CircuitBreaker circuitBreakerWithCustomConfig = circuitBreakerRegistry
  .circuitBreaker("name2", circuitBreakerConfig);

다음과 같이 CircuitBreaker 인스턴스에 설정을 추가할 수도 있습니다.

CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
  .failureRateThreshold(70)
  .build();

circuitBreakerRegistry.addConfiguration("someSharedConfig", config);

CircuitBreaker circuitBreaker = circuitBreakerRegistry
  .circuitBreaker("name", "someSharedConfig");

설정 재정의도 가능합니다.

CircuitBreakerConfig defaultConfig = circuitBreakerRegistry
   .getDefaultConfig();

CircuitBreakerConfig overwrittenConfig = CircuitBreakerConfig
  .from(defaultConfig)
  .waitDurationInOpenState(Duration.ofSeconds(20))
  .build();

Fallback

요청 실패 시, 특정 메서드를 대체로 실행할 수 있도록 해주는 기능입니다.

Retry

실패한 요청을 일정 시간 이후에 재시도 기능을 제공하는 모듈입니다.

Bulkhead

동시 실행(병렬) 횟수 제한 기능을 제공하는 모듈입니다.

RateLimiter

마이크로 서비스 내부 실행 중 일정 비율 이상의 부하를 막기위한 기능을 제공하는 모듈입니다.

TimeLimiter

마이크로 서비스 내부 실행 시간 제한 기능을 제공하는 모듈입니다.

Cache

요청에 대한 결과를 캐싱하는 기능을 제공하는 모듈입니다.


5. Resilience4j 모듈의 우선순위

TargetFunction > BulkHead > TimeLimiter > RateLimiter > CircuitBreaker > Retry

이는 Resilience4j CircuitBreakerConfigurationProperties, RetryConfigurationProperties 클래스 내부르살펴보면, 별도의 처리가 없는 경우, CircuitBreaker가 Retry 보다 우선순위가 높습니다.(Order 값이 클수록 우선순위가 더 높습니다.)

BulkheadConfigurationProperties

CircuitBreakerConfigurationProperties

RetryConfigurationProperties

Resilience4j는 AOP 기반하에 동작합니다. 따라서, 우선순위 변경 시 annotation 방식을 사용하여 layer 를 분리하거나 aspectOrder 속성 값을 수정해줘야 합니다.

profile
백엔드 서버 엔지니어

0개의 댓글