[MSA스터디] 13. Resilience4j를 사용한 탄력성 개선

vector13·2022년 10월 24일
0

서킷 브레이커 사용하는 이유 : 마이크로서비스의 오류를 줄이고 탄력성을 향상

IN) 마이크로서비스 사이의 통신이 이루어지는 대규모 시스템 환경에서
HOW) 동기 방식으로
WHAT) 느리거나 응답하지 않는 downstream 마이크로서비스로 인한 피해를 최소화

Resilience4j의 서킷 브레이커 및 재시도 메커니즘 소개

retry(재시도)와 서킷 브레이커 메커니즘 : 동기 방식으로 연결되는 소프트웨어 컴포넌트 (LIKE 마이크로서비스)에서 특히 유용, 에지 서버를 제외한 모든 마이크로서비스에서 Resilience4j를 사용 可.

  • 에지 서버에선 Resilience4j 사용 不可
    b/c ) 스프링 클라우드 게이트웨이가 예전 서킷 브레이커인 Netflix Hystrix만 지원 하기 때문

이번장은 그림처럼 product-composite 서비스에서 product 서비스를 호출하는 부분에 서킷 브레이커 및 재시도 메커니즘을 적용

서킷 브레이커 소개

상태 다이어그램

  • 서킷 브레이커의 주요 기능
    1 서킷 브레이커는 다량의 오류를 감지 시 -> 서킷을 열어 새 호출 안받음
    2 서킷 브레이커는 서킷이 열려 있을 때 빠른 실패 로직 을 수행 ==> 의존하는 서비스의 응답 중단 때문에 마이크로서비스가 응답하지 못하게 되는 문제를 방지 (고부하 상황에서 특히 유용)
    (== 이어지는 호출에서 시간 초과 등으로 말미암은 새로운 오류가 발생하지 않게 함 +fallback method로 호출을 리디렉션)
    (fallback method: 다양한 비즈니스 로직을 적용시 최적화된 응답을 생성할 수있음 ex. 로컬 캐시의 데이터를 반환하거나 즉각적인 오류 메시지를 반환하는등)
    3 시간이 지나면 -> 서킷 브레이커는 반열림 상태로 전환 돼 새로운 호출을 허용 + 이를 통해 문제를 일으킨 원인이 사라졌는지 확인.
    WHEN) 새로운 오류를 감지
    서킷을 다시 열고 빠른 실패 로직을 다시 수행하며, 오류가 사라졌으면 서킷을 닫고 정상 작동 상태로 돌아감 .

SO) 마이크로서비스는 장애에 대한 탄력성가짐 == 이는 동기 방식으로 통신하는 마이크로서비
스 시스템 환경의 필수 기능

  • 런타임시 서킷 브레이커의 정보를 공개 by Resilience4j
    1 액추에이터의 상태 점검 엔드포인트(actuator/health): 서킷 브레이커의 현재 상태 모니터링
    2 액추에이터 엔드포인트(/actuator/circuitbreakerevents): 서킷 브레이커의 상태 전이 등의 이벤트 게시됨
    3 프로메테우스와 같은 모니터링 도구에 메트릭을 게시 (서킷 브레이커는 스프링 부트의 메트릭 시스템과 통합돼 있기때문)

  • 서킷브레이커 로직 제어하는 Resilience4J를 구성 매개변수
    ringBufferSizeInClosedState: 닫힌 상태에서의 호출 수로, 서킷을 열어야 할지
    결정할 때 사용한다.
    failureRateThreshold: 실패한 호출에 대한 임계값(백분율)으로 이 값을 초과하
    면 서킷이 열린다.
    .. 등이 있음 실습하면서 살펴보겠음

  • 13장에서 구성 설정은

ringBufferSizeInClosedState 5, failureRateThreshold = 50%
(== 마지막 5개의 호출 중 3개 이상이 실패하면 서킷열림 + 2개 이상의 호출이 실패하면 서킷이 다시 열리며, 이 외의 경우엔 서킷이 닫힌다.)

waitInterval = 10000, automaticTransitionFromOpenToHalfOpenEnabled = true
(서킷 브레이커는 10초 동안 서킷을 열린 상태로 유지했다가 반열림 상태로 전환)

ringBufferSizeInHalfOpenState =3
(서킷 브레이커는 서킷이 반열림 상태로 전 환된 후에 처음 유입된 3개의 호출을 기준 삼아 서킷의 열림 여부를 결정)

ignoreExceptions InvalidInputException 및 NotFoundException
(이 비즈니스 예외는 서킷 브레이커에서 오류로 여기지 않음)

재시도 매커니즘

retry 메커니즘
: 설정된 대기 시간을 사이에 두고, 실패한 요청을 여러 번 다시 시도하는 것
++ 일시적인 네트워크 결함과 같이 무작위로 드물게 발생하는 오류에 매우 유용.
사용 조건)
재시도 대상 서비스에 멱등성이 있어야 한다
(==같은 요청 매개 변수로 서비스를 여러 번 호출하더라도 결과는 항상 같아야 함)

Resilience4j는 서킷 브레이커와 같은 방식으로 재시도와 관련된 이벤트 및 메트릭 정보
를 공개
BUT)상태 정보는 전혀 제공X
재시도 이벤트에 관한 정보는 액추에이터 엔드포인트(Jactuator/retryevents)에서 얻을 수 있음

  • 재시도 로직 제어하는 Resilience4J를 구성 매개변수
    maxRetryAttempts: 첫 번째 호출을 포함한 총 재시도 횟수 -> 13장에선 3으로 설정할 것 (==최대 두번재시도)
    waitDuration: 재시도를 다시 수행하기 전의 대기 시간 -> 1000으로 설정 (대기시간 1초)
    retryExceptions: 재시도를 트리거하는 예외 목록 --> InternalServerError로 설정 ( HTTP 요청에 대한 응답으로 500 상태 코드+ 발생한 예외가 InternalServerError인 경우에만 재시도를 트리거)

소스 코드에 서킷 브레이커 및 재시도 메커니즘 추가

임의로 오류를 발생시키고 지연을 삽입하기 위한 코드를 먼저 추가
-> 응답이 늦거나 전혀 응답하지않는 API를 처리하기 위한 서킷 브레이커를 추가 -> 무작위로 발생하는 결함을 처리하 고자 재시도 메커니즘을 추가

프로그래밍 방식으로 지연 및 무작위 오류 추가

1) delay와 2) faultPercentage 쿼리 매개변수 추가

1) : product 마이크로서비스에 있는 getProduct API의 응답을 지연시킴
2) : getProduct API가 쿼리 매개변수로 지정한 백분율(0~100%)에 따라 무작위로 예외를 발생



를 productCompositeService와 productService에 추가


빨간색 나는건 의존성없어서 그런검므로
Resilience4j에 대한 스타터 의존성을 빌드 파일에 추가

product에 무작위 오류 생성 + 지연 삽입

지연함수 - sleep
오류 생성 함수 - 무작위 : 1에서 100 사이의 수가 지정된 오류 백분율보다 크거나 같으면 예외를 발생

서킷 브레이커 추가

서킷 브레이커 및 시간 초과 로직 추가

@CircuitBreaker : 서킷 브레이커가 적용
서킷 브레이커는 시간 초과로 트리거X 예외가 발생해야 트리거O.
WHEN) 시간 초과가 발생하면-> 예외를 생성하는 코드를 추가해 서킷 브레이커를 트리거.

폴백 로직 추가

폴백 로직을 적용하려면 서킷이 열려 있을 때 발생하는 예외인 CircuitBreakerOpenException을 잡아야함

예외를 잡는 getProductFallbackValue

13이면 NotFoundException을 던지고, 이외의 경우엔 하드 코딩된 값을 반환

구성 추가

앞에서 구성 추가한다고 했던거 추가해주기 product-composite.yml

재시도 메커니즘 추가

어노테이션은 아까 서킷 브레이커하면서 붙였고 예외처리 해주고 구성추가

@Retry 메서드에서 던진 예외--> 재시도 메커니즘에 의해 RetryExceptionWrapper 예외로 wrapping.
메서드호출자가 RetryExceptionwrapper 예외를 벗겨 내고 실제 예외로 대체하는 로직을 추가 (IF 메서드에서 던진 예외를 직접 확인해야 하는 상황)( ex. CircuitBreakerOpenException을 잡아서 폴백 메소드를 적용할 때처럼)

서킷 브레이커 및 재시도 메커니즘 사용

빌드 헀더니 test 코드 쪽에서 에러남
ProductCompositeServiceApplicationTests 에 이 코드 넣어주어야함

그렇지만 여전히 test 쪽에서 에러남

product-composite 마이크로서비스에 추가한 product 서킷 브레이커의 상태를 확인하려면
docker run --rm -it --network=my-network alpine wget product-composite:8080/actuator/ health -90 - | jq -r .details.productCircuitBreaker.details.state

서킷 브레이커 닫혀있는 상태에서

for((n=0; n<3; n++))
do
assertCurl 500 "curl -k https://$HOST:$PORT/product-composite/$PROD_ID_REVS_
RECS?delay=3 $AUTH -s"
message=$(echo $RESPONSE jq -r.message)
assertEqual "Did not observe any item or terminal signal within 2000ms'
"${message:0:57}"
done

이용해서 세번 반복하면 --> 서킷브레이커 열림 ==> product 서비스의 늦은응답 때문에 커맨드는 모두 실패

서킷이 열려 있으면 "빠른 실패 로직이 실행되므로" 시간 초과를 기다리지 않고 바로 응답을
반환하고 폴백 메서드를 호출해 최적화된 응답을 생성가능

  • 폴백 메서드에 있는 404 오류를 시뮬레이션하는 로직이 예상대로 작동하는지도 확인
    (13을 주면 404 반환하도록 설정했던거)
    assertCurl 404 "curl -k https://$HOST:$PORT/product-composite/$PROD_ID_NOT_FOUND $AUTH -s" assertEqual "Product Id: $PROD_ID_NOT_FOUND not found in fallback cache!" "$(echo $RESPONSE | jq -r .message)"

구성한 대로 서킷 브레이커는 10초가 지나면 반열림 상태로 전환 --> sleep 10 던지고 -> 정상 요청을 보내는 테스트를 세 번 실행 ==> 닫힘 상태인지 확인

assertEqual "HALF_OPEN" "$($EXEC wget product-composite:8080/actuator/health -go - jq
-r.details.productcircuitBreaker.details.state)"
for ((n=0; n<3; n++))
do
assertCurl 200 "curl -k https://$HOST:$PORT/product-composite/$PROD_ID_REVS_RECS
$AUTH -S"
assertEqual "product name C" "$(echo "$RESPONSE" | jq -r .name)"
done
assertEqual "CLOSED" "$($EXEC wget product-composite:8080/actuator/health -90 - | jq -r
.details.productCircuitBreaker.details.state)"

정리 및 느낀점

13장에서는 Resilience4j를 사용해봣다.
build도 안되고 bash 테스트 스크립트로도 테스트를 못하니 좀 답답했다

Resilience4j, 서킷 브레이커, 재시도 메커니즘의 작동 방식을 살펴봤는데, 서킷 브레이커는 서킷이 열려 있을 때 빠른 실패, 폴백 메서드를 작동시키고 이를 이용해 동기 서비스가 정상적으로 응답하지 않을 때도 서비스가 응답 불능 상태에 빠지지는 것을 미연에 방지한다. 서킷이 반열림 상태에 있을 때는 장애가 발생한 서비스가 정상 상태로 돌아왔는지 확인하고, 다시 요청을 처리할 수 있도록 서킷을 닫아서 마이크로서비스의 탄력성을 높이는 기능을 가지고 있다.

재시도 메커니즘은 가끔 발생하는 문제(LIKE 일시적인 네트워크 결함) 으로 전송하지 못한 요청을 재전송 시도 ==> 멱등성 있는 서비스에만 적용 가능

MSA2 스터디가 한장을 앞두고 있다는 것이 놀랍다
이론상으로라도 서비스의 탄력성을 가지게 하는 얘를 알게돼서 좋다...
여전히 프로젝트 적용에는 아직 무리인 것 같은 생각은 동일하다
나머지 마지막 한 장도 파이팅..

profile
HelloWorld! 같은 실수를 반복하지 말기위해 적어두자..

0개의 댓글