본문 바로가기
스프링

[스프링] @CircuitBreaker 적용하기

by kdohyeon (김대니) 2023. 2. 16.
반응형

MSA 환경에서는 작은 규모의 서비스가 상호작용하며 서로 연결되어 있다. 이런 환경에서 하나의 서비스가 장애가 나 서비스가 중단되면 다른 서비스들로 그 장애가 전파될 수 있다. 장애 전파를 막을 수 있는 여러 가지 방법들이 존재하는데, 그 중 하나는 "서킷 브레이커" 이다.

"서킷 브레이커" 라는 용어는 원래 전기 회로에서 과열된 회로를 차단하는 장치를 의미하는데, 보통 주식에서 많이 접한다. 주가가 특정 % 이상 급락하는 경우 발동하며 매매를 일시 정지할 수 있는 제도이다. 마찬가지로 MSA 환경에서의 서킷 브레이커는 발생한 장애를 다른 서비스로 전파하지 않도록 하기 위한 장치라고 보면 된다.

서킷 브레이커 종류

  • Netflix Hystrix. 넷플릭스에서 만든 서킷 브레이커로 SpringBoot 2.4 버전 부터는 지원이 안된다.
  • Resilience4j. 넷플릭스 서킷 브레이커가 지원 종료되면서 많이 사용하는 라이브러리

서킷 브레이커 상태

크게 3가지 상태가 존재한다. OPEN (열림), HALF-OPEN (반-열림), CLOSED (닫힘)

https://resilience4j.readme.io/docs/circuitbreaker

CLOSED

  • 초기 상태
  • 모든 요청을 그대로 허용

OPEN

  • 에러율이 특정 임계치를 넘어서면 CLOSED 에서 OPEN 으로 변경
  • 모든 요청은 차단
  • 다운 스트림으로의 요청은 보내지 않고, 바로 에러를 발생시킴

HALF-OPEN

  • OPEN 상테에서 일정 시간이 지나면 HALF-OPEN 상태로 변경
  • 요청을 다시 다운 스트림으로 보내 성공하는지 확인
  • 특정 임계치만큼 성공하면 다시 CLOSED 상태로 변경, 실패하면 OPEN 상태로 변경

의존성

  • resilience4j 에서 제공하는 서킷 브레이커를 사용한다.
  • @CircuitBreaker 어노테이션을 부여하여 스프링 AOP 기능을 사용해야 하기 때문에 aop 의존성도 추가해준다.
implementation("io.github.resilience4j:resilience4j-spring-boot2")
implementation("org.springframework.boot:spring-boot-starter-aop")

설정 (application.yml)

  • 설정은 application.yml 파일에 적용해두었다.
  • 기본적으로 아래 기본 property 만 적용했고, 추가로 필요한 property 는 여기를 참고하면 된다.
resilience4j.circuitbreaker:
  configs:
    default:
      failureRateThreshold: 50 # 실패한 호출에 대해 서킷이 열리게 되는 임계값 (백분율)
      slowCallRateThreshold: 100 # 느린 호출에 대해 서킷이 열리게 되는 임계값 (백분율)
      slowCallDurationThreshold: 5s # 느린 호출로 판단하기 위한 기준시간
      slidingWindowType: COUNT_BASED # 서킷이 `CLOSED` 상태에서 호출결과를 기록할때 사용하는 슬라이딩 윈도우 타입. 카운트 또는 시간기반으로 동작한다.
      slidingWindowSize: 10 # 카운트기반으로 동작할때는 카운트 수, 시간기반으로 동작할때는 초
      waitDurationInOpenState: 5s # 서킷이 `OPEN` 상태에서 `HALF_OPEN` 상태로 전환하기까지 시간
  instances:
    BlogHttpClient:
      registerHealthIndicator: true
      baseConfig: default

코드

  • 블로그 문서를 조회하는 searchBlogDocuments 메소드에 @CircuitBreaker 어노테이션을 부여한다
  • 만약 기본으로 호출하는 callKakao 메소드가 실패하면 fallbackMethod 로 기입되어 있는 callNaver 메소드가 실행된다.
@Slf4j
@Component
public class BlogHttpClient implements BlogPort {

    private final KakaoBlogHttpClient kakaoBlogHttpClient;
    private final NaverBlogHttpClient naverBlogHttpClient;

    public BlogHttpClient(KakaoBlogHttpClient kakaoBlogHttpClient, NaverBlogHttpClient naverBlogHttpClient) {
        this.kakaoBlogHttpClient = kakaoBlogHttpClient;
        this.naverBlogHttpClient = naverBlogHttpClient;
    }

    @Override
    @CircuitBreaker(name = "BlogHttpClient", fallbackMethod = "callNaver")
    public Blog searchBlogDocuments(BlogSearchClause clause) {
        return callKakao(clause);
    }

    public Blog callKakao(BlogSearchClause clause) {
        return kakaoBlogHttpClient.searchBlogDocuments(KakaoBlogSearchClause.of(clause));
    }

    public Blog callNaver(BlogSearchClause clause, Throwable e) {
        log.warn("카카오 블로그 조회에 실패하여 네이버 블로그 조회로 변경합니다. errMsg: {}", e.getMessage(), e);
        return naverBlogHttpClient.searchBlogDocuments(NaverBlogSearchClause.of(clause));
    }
}
반응형

댓글