서비스 장애 대응의 필요성
MSA구조에서는 각 서비스가 독립적으로 동작하며, 서비스 간의 의존성을 최소화해야 한다.
내 프로젝트에서 Order-service에서 다른 서비스로의 요청이 많다.
예를 들면 user-service에 사용자 정보 요청이라던가, product-service에 상품요청을 한다.
그렇다면 이 상황에서 order-service에서 요청을 보내주는 다른 서비스들에서 응답이 지연되거나 실패하는 경우 어떻게 될까?
Order-service의 리소스(예: 스레드풀)가 고갈되어 전체 시스템에 영향을 미쳐 전체 서비스의 장애로 이어질 것이다.
이러한 사항들을 해결하기 위해 Circuit Breaker 패턴을 사용할 것이다
Circuit Breaker 패턴 을 구현하면 장애를 빠르게 탐지하고 요청을 차단할 수 있다!
Circuit Breaker 구현
다른 서비스가 불안정 하거나 응답 시간이 길어질때, 서킷 브레이커 패턴을 사용하여 문제를 격리하고 시스템의 안정성을 유지 할수 있다. 서킷 브레이커는 일정 시간 동안 실패가 지속되면 요청을 차단하고, 일정 시간이 지나거나 시스템이 안정되면 다시 요청을 시도하게 해준다.
서킷브레이커에는 두가지 라이브러리가 있다.
- Netflix Hystrix
- Resilience4J
Netflix Hystrix는 더이상 개발이 진행되지 않는다. 현재 Spring-cloud 에도 내장되어있는 Resilience4j 사용할것이다!
Resilience4j의 모듈
- resilience4j-circuitbreaker: 회로 차단기
- resilience4j-ratelimiter: Rate limiting
- resilience4j-bulkhead: Bulkheading
- resilience4j-retry: 실패한 실행을 반복합니다.
- resilience4j-timelimiter: Timeout handling
- resilience4j-cache: Result caching
Resilience4j 원리
1. Circuit Breaker의 3가지 상태
Circuit Breaker는 Closed, Open 외에 Half_Open 상태가 존재하여 총 3가지 상태가 존재한다
Circuit Breaker의 상태가 결정되는 기준인 '실패 임계치'가 존재
실패 임계치를 기준으로 Open, Close 상태가 결정되고, 이후에 Half_Open 상태로 진입하게 된다.
- Closed : 요청의 실패율이 설정한 실패 임계치보다 낮은 상태
- Circuit Breaker가 Closed 상태면, 해당 서비스에 정상적으로 모든 요청이 처리된다.
- Open : 요청의 실패율이 설정한 실패 임계치보다 높은 상태
- Circuit Breaker가 Open 상태면, 해당 서비스로 요청을 보내지 않고 즉시 실패처리를 한다.
- Half_Open : Open 상태 이후에 일정 시간이 지난 상태
- Circuit Break가 Half_Open 상태라면 이후 요청의 성공/실패 상태에 따라 Closed/Open 상태로 변경된다.
- 이후 요청이 성공한다면 Closed 상태가 되어 모든 요청이 정상 처리 상태로 돌아간다.
- 이후 요청이 실패한다면 다시 Open 상태가 되어 모든 요청을 즉시 차단한다.
- Circuit Break가 Half_Open 상태라면 이후 요청의 성공/실패 상태에 따라 Closed/Open 상태로 변경된다.
2. Fallback
Fallback은 서비스를 차단한 경우 예외를 발생시키는 대신 대비책을 제공하거나 미리 준비된 동작을 실행하는 것이다.
Circuit Breaker가 Open 상태인 상황에서 사용자 요청을 에러로 응답하지 않고 성공으로 응답하는 것
3.Bulkhead
Bulkhead는 응답시 지연되는 서비스에 자원을 모두 소진하지 않도록 스레드 풀을 격리하는 것이다.
Resilience4j 적용하기
Resilience4j - circuitbreaker
장애를 방지하기 위한 패턴으로 장애 발생 지점을 감지하고 실패하는 요청을 계속적으로 보내지 않도록 방지하는 패턴
의존성 추가
order-service에 의존성 추가 해준다.
implementation group: 'io.github.resilience4j', name: 'resilience4j-spring-boot3', version: '2.2.0'
implementation group: 'io.github.resilience4j', name: 'resilience4j-circuitbreaker', version: '2.2.0'
application.yml 설정
resilience4j:
circuitbreaker:
instances:
userService:
registerHealthIndicator: true # 서킷 브레이커의 상태를 스프링 부트 액추에이터에서 확인할 수 있도록 헬스 인디케이터를 등록
slidingWindowSize: 10 # 슬라이딩 윈도우 크기를 10으로 설정, 이 기간 동안의 호출 결과를 기반으로 서킷 브레이커의 상태를 결정
permittedNumberOfCallsInHalfOpenState: 5 # 서킷 브레이커가 Half-Open 상태일 때 허용되는 호출 수를 5로 설정, 이 호출들로 서킷 브레이커를 다시 Closed 상태로 전환할지 결정
waitDurationInOpenState: 10000ms # 서킷 브레이커가 Open 상태에서 대기하는 시간을 10초(10000ms)로 설정, 이후 Half-Open 상태로 전환
failureRateThreshold: 50 # 실패율 임계값을 50%로 설정, 슬라이딩 윈도우 내에서 호출의 50% 이상이 실패하면 서킷 브레이커가 Open 상태로 전환
...
- registerHealthIndicator: true
- 서킷 브레이커의 상태를 스프링 부트 헬스 체크에서 확인 여부
- slidingWindowSize
- 서킷 브레이커의 상태를 결정할때 참고할 호출 결과의 개수
- permittedNumberOfCallsInHalfOpenState
- 서킷 브레이커가 Half-Open 상태일때 허용되는 호출수, 이 호출들이 성공하면 서킷 브레이커가 Closed 상태로 전환
- waitDurationInOpenState
- 서킷브레이터가 open인 상태에서 유지되는 대기 시간을 설정, 이 시간이 지나면 서킷 브레이터는 Half-Open 상태로 전환
- failureRateThreshold
- 서킷브레이커가 Open 상태로 전환되는 실패율의 임계값을 설정, 지정된 슬라이딩 윈도우 크기 내에서 실패율이 이 값을 초과하면 서킷 브레이커가 open상태로 전환
Service설정
@CircuitBreaker(name = "userService", fallbackMethod = "memberServiceFallback")
public AddressResponseDto getAddress(Long addressId, String email) {
...
}
public AddressResponseDto memberServiceFallback(Long addressId, String email, Throwable throwable) {
log.error("Member Service is down: {}", throwable.getMessage());
return new AddressResponseDto();
}
- name : yml에 설정한 서킷브레이커의 이름을 적용
- fallbackMethod : 실패시 불백으로 호출할 메서드 이름 작성
Resilience4j - retry
서비스 호출이 실패했을 때 자동으로 재시도를 시도하는 메커니즘이다.
의존성 추가
implementation group: 'io.github.resilience4j', name: 'resilience4j-retry', version: '2.2.0'
application.yml 설정
retry:
instances:
userService:
maxAttempts: 3 # userService에 대해 최대 3번까지 재시도를 허용합니다.
waitDuration: 500ms # 각 재시도 사이에 500밀리초의 대기 시간을 설정합니다.
- maxAttempts
- 최대 재시도할 횟수 지정
- waitDuration
- 각 재시도 사이의 대기 시간 설정
Service설정
@CircuitBreaker(name = "userService", fallbackMethod = "memberServiceFallback")
@Retry(name = "userService", fallbackMethod = "memberServiceFallback")
public AddressResponseDto getAddress(Long addressId, String email) {
...
}
Resilience4j - timelimiter
외부 서비스 호출이나 비동기 작업에 대한 최대 실행 시간을 제한하는 기능을 제공한다.
의존성 추가
implementation group: 'io.github.resilience4j', name: 'resilience4j-timelimiter', version: '2.2.0'
application.yml 설정
timelimiter:
instances:
default:
timeout-duration: 5s # 기본 타임아웃 시간을 5초로 설정합니다. 이 시간 내에 작업이 완료되지 않으면 타임아웃 예외가 발생합니다.
Service설정
@CircuitBreaker(name = "userService", fallbackMethod = "memberServiceFallback")
@Retry(name = "userService", fallbackMethod = "memberServiceFallback")
@TimeLimiter(name = "userService")
public AddressResponseDto getAddress(Long addressId, String email) {
...
}
'프로젝트 > Springboot-MSA- PreOrder' 카테고리의 다른 글
[Project] Redis 캐싱 성능개선 (0) | 2024.09.01 |
---|---|
[Project] 데이터 동시성 문제 해결 (4) | 2024.08.26 |
[Project] API Gateway (0) | 2024.08.18 |
[Project] Spring Batch란? (0) | 2024.08.18 |
[Project] Service Discovery (0) | 2024.08.18 |