Resilience4j 도입
플랫폼이 제공하는 웨이팅 서비스는 사용 경험에 알림이 크게 작용한다.
알림이 안정적으로 고객에게 전달되야 한다.
알림이 고객에게 발송되기까지 외부 시스템과 통신이 많이 발생한다. 유저 서버, 레스토랑 서버에서 연락처를 못 받아오면 알림 메시지를 생성할 수 없고 슬랙 서버와 통신이 되지 않으면 알림을 실제로 전송할 수 가 없다.
Resilience4j 적용 설계
유저, 레스토랑 서버
통신 실패 → 재시도 → 재시도 실패 시 운영진에게 서비스 이상 알림 발송
슬랙 서버
통신 실패 → 재시도 → 재시도 실패 시 CircuitBreaker 활성화 → 이메일 발송 대체 로직 실행
서비스 이상 알림 발송
Resilience4j는 Micrometer 를 통해 메트릭 데이터를 수집하는데, Prometheus와 통합이 쉽게 가능해 Grafana를 통해 시각화로 이어질 수 있다. Grafana에는 조건을 설정하여 알림을 보낼 수 있기 때문에 서킷브레이커의 상황을 설정하여 운영진에게 서비스 이상 알림을 전달하는 것이 가능해진다.
의미있는 Retry를 하기 위해
일시적인 문제일 수 있으니 재시도를 하지만 단순하게 재시도를 반복하는 것은 많은 경우 해결책이 되지 못하거나 오히려 네트워크 부담을 가중시킨다.
Backoff
그래서 일반적 방법은 재시도 간격을 지수(제곱)에 비례하여 점점 늘리는 Exponential Backoff
전략을 사용한다. 토스 증권에서도 이런 이슈를 해결하기 위해 이 전략을 사용했다. 하지만 Backoff만 적용되면 실패한 시도들이 미래에 다시 같은 시점에서 만나서 경쟁하기 때문에 새로운 문제가 발생한다. AWS 기술블로그에서 Backoff를 Jitter 없이 사용하는 것은 **“the clear loser. “ 명백한 패배자라고 말하는 것을 볼 수 있다.
Jitter
지연되어 같은 시간대에 재시도하는 접근을 무작위성을 부여하여 분산시키는 방법이다.
Backoff 적용
Backoff + Jitter 적용
Circuit Breaker
상태
- CLOSED: 정상적일때
- OPEN: 열려있을때
- HALF_OPEN : CLOSED로 가기위한 대기상태
슬라이딩 윈도우 알고리즘
Count based
- 만약 호출 수가 그리 많지 않은 API거나 신속하게 Circuit을 오픈해야하는 경우 → Count based
Time-based
- N초 동안 많은 집계 표본을 통해 신중하게 오픈 여부를 결정하는 경우라면 → Time based
서비스를 이용하다가 웨이팅 입장 알림을 못 받는 사용자가 늘어날수록 피해가 크리티컬 하기 때문에 Count based 방식을 선택.
호출 거부
Circuit이 OPEN 상태이면 CallNotPermittedException
을 발생시키고 호출을 거부한다. 일정 시간이 지난후 시스템이 정상이 되었는지 확인하기 위해 몇개나 호출할지 설정도 가능하다.
설정
- Bean 설정
- yaml 기반 설정
속성
위의 내용을 속성으로 값으로 반영했다. 자세한 속성 설명은 공식 문서로도 확인할 수 있다.
강의에서 말했듯이 최소의 설정을 default로 잡고 어플리케이션의 구조에서 문제 없는지 확인한 후 나중에 가중치를 수정해나가기로 한다.
resilience4j:
circuitbreaker:
configs:
default:
registerHealthIndicator: true // actuator - 매트릭 추가 여부
eventConsumerBufferSize: 10 // actuator - 발생한 이벤트 버퍼 크기
slidingWindowType: COUNT_BASED // 알고리즘 - 타입
slidingWindowSize: 10 // 알고리즘 - 범위, 최근 n회 기준
failureRateThreshold: 20 // 실패 - 임계 값 퍼센트
minimumNumberOfCalls: 10 // 실패 - 집계에 필요한 최소 호출 수
slowCallRateThreshold: 80 // 지연 - 느린 호출의 비율 %
slowCallDurationThreshold: 30000 // 지연 - 느린 호출의 기준 (밀리초)
permittedNumberOfCallsInHalfOpenState: 3 // Half-open 상태에서 최대 호출 수
waitDurationInOpenState: 60s // Half-open 전환 대기 시간
automaticTransitionFromOpenToHalfOpenEnabled: true
참고자료
- Resilience4J 6 : 서킷 브레이커 구현 실습
- resilience4j/retry/IntervalFunction.java
- 슬라이딩 윈도우 알고리즘(Sliding Window)
- Spring with Resilience4j — Circuit breaker 적용하기
ERROR
추측 1. KafkaMessageListenerContainer
Kafka가 발생한 예외를 별도로 처리하고 있어서 Resilience4j 서킷 브레이커의 예외 감지가 제대로 동작하지 않는다.
추측 2. kafka.listener.DefaultErrorHandler
예외가 발생할때 카프카 트랜잭션 롤백이 우선 처리되어 서킷 브레이커가 개입할 수 없다.
- 환경설정 추가 - 해결 X
isolation-level: read_committed // 커밋된 레코드만 읽는다.
auto-offset-reset: earliest // 카프카에서 초기 오프셋이 없거나 현재 오프셋이 더이상 존재하지 않은 경우에 가장 초기 오프셋 값으로 설정
enable-auto-commit: false // 백그라운드로 주기적으로 오프셋을 커밋할지 여부
- Bean 등록 - 해결 X
Kafka로 처리하는 방법도 고려
카프카를 통한 retry 방법도 존재. backoff도 지원하지만 jitter를 topic으로 관리해야하는 듯 보인다.