서비스의 실패란?
장애다.
Resilience4j는 장애에 강한 서비스를 만드는 라이브러리다.
1)다른 서버로 보낸 API 요청이 실패하면? 일시적으로 실패하면 다시 요청할 수 있다. --> Retry 모듈로 처리할 수 있다.
2)서버가 너무 많은 트래픽을 받아서 잠시 트래픽을 차단시켜야 한다면? -->서킷브레이커 적용(원래 기능을 대체하는 fallback을 실행)
평점을 별도로 조회한다고 해보자.
리뷰 저장소에 문제가 생김 -->리뷰 조회가 실패 or 조회 속도가 너무 느려짐
(방어로직이 없다면, API 요청이 에러로 응답이 올 것임)
-->이런 상황에 적용하기 적절
우선, 리뷰 정보가 없더라도 상품 정보만이라도 뜨는 게 좋다.
100번의 요청 중 20번 이상 실패하면 비정상 상태로 가정해서 기능 실행 차단
(부하가 높은 상태에서 이를 회복하려면 일시적으로 트래픽을 차단하는 게 도움이 될 수 있음 -->빠르게 회복)
필요한 의존성
dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:2023.0.3")
}
}
// for resilience4j
implementation("org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j")
// 아래 의존성 추가하지 않으면 @CircuitBreaker 어노테이션이 동작하지 않음
implementation("org.springframework.boot:spring-boot-starter-aop")
아렇게 의존성을 추가하고
resilience4j.retry.configs.default.max-attempts=3
resilience4j.retry.configs.default.wait-duration=1000 //각각 시도마다 1초씩 기다린다
resilience4j.retry.configs.default.retry-exceptions= com.plaything.api.common.exception.CustomException //재시도
resilience4j.retry.configs.default.ignore-exceptions= com.plaything.api.common.exception.CustomException //재시도 무시x, 등록 안된 예외들은 모두 무시
resilience4j.retry.instances.simpleRetryConfig.base-config=default
여러 메서드에서 공유할 수 있도록 인스턴스로 만드는 것.
retry는 상태x 그래서 여기서는 심각하게 생각 x
@Slf4j
@Service
public class RetryService {
private static final String SIMPLE_RETRY_CONFIG= "simpleRetryConfig";
//fallback 문자열을 찾아서 retry에 모두 실패했을 때 그 메서드를 실행
//실패한 상황을 저장하고, 로그를 남길 수 있을 것
@Retry(name =SIMPLE_RETRY_CONFIG , fallbackMethod ="fallback")
public String process() throws BadRequestException {
throw new BadRequestException();
}
//예외를 받아야 한다
private String fallback(Throwable throwable){
return "fallback";
}
}
이렇게 쓸 수 있다.
이 Retry를 어디에 쓸 수 있을까?
1)실패하면 안 되고
2)외부 API와 연결되는 지점에 사용할 수 있으며
3)일시적 오류 가능성이 있는 곳(네트워크 불안정)이다.
지금 당장은 프로젝트에 적용할 만한 부분은 없는 것으로 보인다.
1)리뷰 저장소에 문제가 생겨도 상품 목록은 정상적으로 보여줄 수 있다.
2)많은 부하를 받은 리뷰 저장소가 회복될 동안 트래픽을 차단해서
리뷰 저장소의 회복을 돕는다.
이 새로고침도 트래픽이 발생해서 이미 바쁜 리뷰 저장소에 부하만 커진다.
Closed 상태는 정상적 상태(트래픽이 들어온다)
Open는 트래픽이 차단된 상태
Half_open은 차단된 상태에서 정상적인 상태로 갈 수 있는지 점검하는 상태
장애가 x%이상 발생하면 서킷브레이커가 작동해서 레디스로 가는 흐름을 끊고
미리 설정해둔 fallback 메서드를 사용하게 된다.
resilience4j.circuitbreaker.configs.default.sliding-window-type= COUNT_BASED
resilience4j.circuitbreaker.configs.default.minimum-number-of-calls= 7
//최소 7번까지는 무조건 closed로 가정하고 호출
resilience4j.circuitbreaker.configs.default.sliding-window-size= 10
//minimum-number-of-calls이후로는 10개의 요청을 기준으로 판단
resilience4j.circuitbreaker.configs.default.wait-duration-in-open-state= 10s
//open상태에서 Half_open으로 가려면 얼마나기다릴 것인지?
resilience4j.circuitbreaker.configs.default.failure-rate-threshold= 40
//slidingWindowSize 중 몇 % 가 recordException이면 open으로 만들 것인가?
resilience4j.circuitbreaker.configs.default.slow-call-duration-threshold = 3000
//몇 ms동안 요청이 처리되지 않으면 실패로 간주할 것인가
resilience4j.circuitbreaker.configs.default.slow-call-rate-threshold= 60
//slidingWindowSIZE중 몇 slowCall이면 open으로 만들 것인가
resilience4j.circuitbreaker.configs.default.permitted-number-of-calls-in-half-open-state=5
//HALF_OPEN에서 5번까지는 CLOSED로 가기 위해 호출한다.
resilience4j.circuitbreaker.configs.default.automatic-transition-from-open-to-half-open-enabled=true
//OPEN 상태에서 자동으로 HALF_OPEN으로 갈것인가?
resilience4j.circuitbreaker.configs.default.event-consumer-buffer-size= 10
//actuator를 위한 버퍼 사이즈
resilience4j.circuitbreaker.configs.default.record-exceptions[]= org.apache.coyote.BadRequestException
resilience4j.circuitbreaker.configs.default.ignore-exceptions[]=
resilience4j.circuitbreaker.instances.simpleCircuitBreakerConfig.base-config=default```
설정 파일에 내용을 추가하면 된다.
서킷브레이커의 상태 변화를 로그로 확인하려면
@Bean
public RegistryEventConsumer<CircuitBreaker> myRegistryEventConsumer() {
return new RegistryEventConsumer<CircuitBreaker>() {
@Override
public void onEntryAddedEvent(EntryAddedEvent<CircuitBreaker> entryAddedEvent) {
log.info("RegistryEventConsumer.onEntryAddedEvent");
entryAddedEvent.getAddedEntry().getEventPublisher()
.onEvent(event -> log.info(event.toString()));
entryAddedEvent.getAddedEntry()
.getEventPublisher()
.onFailureRateExceeded(event -> log.info("FailureRateExceeded: {}", event.getEventType()));
}
@Override
public void onEntryRemovedEvent(EntryRemovedEvent<CircuitBreaker> entryRemoveEvent) {
log.info("RegistryEventConsumer.onEntryRemovedEvent");
}
@Override
public void onEntryReplacedEvent(EntryReplacedEvent<CircuitBreaker> entryReplacedEvent) {
log.info("RegistryEventConsumer.onEntryReplacedEvent");
}
};
}
이걸 메인 클래스에 넣어주면 된다.
이렇게 여러번 실패하면 위처럼 open 상태로 바뀐다.
open 상태일때는 마지막 로그처럼 요청이 허용되지 않고 바로 fallback 메서드가 실행됐다.
half_open 상태일 때 요청이 정상 처리되기도 했다.
HALF_OPEN 일때 요청이 여러번 성공하면 CLOSED로 변경됐다.
slowcall 에 대해서 설정을 해 놓았기에
이 slowcall이 많이 발생하면 open으로 변경됐다.