CircuitBreaker로 Retry, 흐름 차단 시도하기

Alex·2024년 11월 20일
0

Plaything

목록 보기
19/118
post-thumbnail

Resilience4j란?

서비스의 실패란?

장애다.

Resilience4j는 장애에 강한 서비스를 만드는 라이브러리다.

1)다른 서버로 보낸 API 요청이 실패하면? 일시적으로 실패하면 다시 요청할 수 있다. --> Retry 모듈로 처리할 수 있다.

2)서버가 너무 많은 트래픽을 받아서 잠시 트래픽을 차단시켜야 한다면? -->서킷브레이커 적용(원래 기능을 대체하는 fallback을 실행)

Retry

  • 몇번까지 재시도할 것인가?
  • 재시도 간격은 얼마나 길게 줄 것인가?
  • 어떤 상황을 호출 실패로 간주할 것인가?
    (어떤 예외를? 대부분 네트워크 통신, NullPointer 예외는 재시도 해도 의미가 없을 수 있음)

CircuitBreaker

평점을 별도로 조회한다고 해보자.

리뷰 저장소에 문제가 생김 -->리뷰 조회가 실패 or 조회 속도가 너무 느려짐
(방어로직이 없다면, API 요청이 에러로 응답이 올 것임)

-->이런 상황에 적용하기 적절

우선, 리뷰 정보가 없더라도 상품 정보만이라도 뜨는 게 좋다.

100번의 요청 중 20번 이상 실패하면 비정상 상태로 가정해서 기능 실행 차단
(부하가 높은 상태에서 이를 회복하려면 일시적으로 트래픽을 차단하는 게 도움이 될 수 있음 -->빠르게 회복)

필요한 의존성

  • 스프링 aop(이 기능을 많이 쓴다)
  • actuator

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")
    
    
    

아렇게 의존성을 추가하고

retry 설정

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)일시적 오류 가능성이 있는 곳(네트워크 불안정)이다.

지금 당장은 프로젝트에 적용할 만한 부분은 없는 것으로 보인다.

CircuitBreaker 알아보기

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으로 변경됐다.

profile
답을 찾기 위해서 노력하는 사람

0개의 댓글