본문 바로가기
Java, Kotlin, Spring

[Java/Spring] Resilience4j - Circuit Breaker (3). 적용

by 댕댕미냉 2023. 2. 13.

Resilience4j

 

Resilience4j - Circuit Breaker (3). 적용

이전 2개의 포스트에서 서킷브레이커는 어떻게 작동하는지, Resilience4j의 서킷브레이커를 개발자의 의도대로 설정하는 방법에 대해 알아보았습니다. 마지막 포스트는 앞서 설정한 서킷브레이커를 적용시키는 방법 & 테스트하는 방법에 대해 작성하였습니다.

 

 

 

Circuit Breaker 적용하기

생성된 서킷브레이커는 아래와 같이 @CircuitBreaker 어노테이션을 사용하여 쉽게 적용할 수 있습니다. 적용할 때, 어떤 서킷브레이커를 사용할 지(name), 그리고 에러가 발생한 경우 어떤 메서드를 통해 처리할 지(fallbackMethod) 지정해야 합니다. 다음 코드는 서킷브레이커의 작동 여부를 확인하기 위해 간단하게 짜놓은 로직입니다. 아래 예시는 앞선 포스트에서 설정한 2가지 서킷브레이커의 인스턴스를 생성하고, 각각을 적용하기 위해 짠 코드입니다.

 

import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class CircuitBreakerService {

    public static final String CIRCUIT_BREAKER_NAME = "testService";
    public static final String CUSTOM_CIRCUIT_BREAKER_NAME = "test";

    @CircuitBreaker(name = CIRCUIT_BREAKER_NAME, fallbackMethod = "fallback")
    public void testMethodWithYml(String text) {
        log.debug("[YML]test Method is Running : " + text);
        if (Integer.parseInt(text) > 5) {
            throw new RuntimeException("Runtime Exception");
        }
    }

    @CircuitBreaker(name = CUSTOM_CIRCUIT_BREAKER_NAME, fallbackMethod = "fallback")
    public void testMethodWithConfig(String text) {
        log.debug("[CONFIG]test Method is Running : " + text);
        if (Integer.parseInt(text) > 5) {
            throw new RuntimeException("Runtime Exception");
        }
    }

    private void fallback(String text, Exception ex) {
        log.error("Fallback Method is Running : " + text);
    }

    private void fallback(String text, CallNotPermittedException ex) {
        log.error("CircuitBreaker OPEN : " + text);
    }

}

 

앞선 포스트에서 말했듯이, 서킷브레이커의 상태가 OPEN이 되면 CallNotPermittedException을 발생시킵니다. 서킷브레이커가 OPEN이 되었을 때 특정 로직으로 돌아가게끔 하고 싶다면, CallNotPermittedException을 받는 fallback 메서드를 만들어서 적용해야 합니다.

 

 

Circuit Breaker 작동 여부 테스트

Circuit Breaker의 작동 여부는 메서드에 로그를 찍어서도 확인할 수 있지만, 직접 State를 비교해서 정상적으로 작동하는지 확인할 수도 있습니다. 설정된 최소 실행 횟수를 넘기며, 지정한 임계치를 넘어 상태가 OPEN으로 변경되게 끔 하였으며, 이후 다시 서킷브레이커가 상태를 HALF_OPEN -> CLOSED 까지 변경시킨 후 다시 OPEN 되는지 여부까지 확인하고 있습니다.

 

import static com.tistory.mein-figur.service.CircuitBreakerService.CIRCUIT_BREAKER_NAME;
import static org.assertj.core.api.Assertions.assertThat;

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreaker.State;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@SpringBootTest(
        properties = {"spring.config.location=classpath:application-core.yml"}
)
@Transactional
public class CircuitBreakerTest extends TestBase {

    private static final String CUSTOM_CIRCUIT_BREAKER_NAME = "test";

    @Autowired
    private CircuitBreakerService service;
    @Autowired
    private CircuitBreakerRegistry circuitBreakerRegistry;

    @BeforeEach
    void setUp() throws InterruptedException {
        Thread.sleep(6000);
    }


    @Test
    @DisplayName("서킷브레이커 테스트(yml)")
    void simpleTest() throws InterruptedException {
        CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(CIRCUIT_BREAKER_NAME);
        log.info(String.valueOf(circuitBreaker.getCircuitBreakerConfig()));

        for (int i = 0; i < 30; i++) {
            service.testMethodWithYml(String.valueOf(i));
//            log.info(String.valueOf(circuitBreaker.getState()));
        }
        checkHealthStatus(circuitBreaker, State.OPEN);

        Thread.sleep(6000);

        for (int i = 0; i < 30; i++) {
            service.testMethodWithYml(String.valueOf(i));
        }
        checkHealthStatus(circuitBreaker, State.OPEN);
    }

    @Test
    @DisplayName("서킷브레이커 테스트(config)")
    void simpleTest2() throws InterruptedException {
        CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(CUSTOM_CIRCUIT_BREAKER_NAME);
        log.info(String.valueOf(circuitBreaker.getCircuitBreakerConfig()));

        for (int i = 0; i < 30; i++) {
            service.testMethodWithConfig(String.valueOf(i));
//            log.info(String.valueOf(circuitBreaker.getState()));
        }
        checkHealthStatus(circuitBreaker, State.OPEN);

        Thread.sleep(6000);

        for (int i = 0; i < 30; i++) {
            service.testMethodWithYml(String.valueOf(i));
        }
        checkHealthStatus(circuitBreaker, State.OPEN);
    }

    private void checkHealthStatus(CircuitBreaker circuitBreaker, State state) {
        assertThat(circuitBreaker.getState()).isEqualTo(state);
    }



}

 

 

 

 

2023.02.11 - [Spring] - [Java/Spring] Resilience4j - Circuit Breaker (1). 정의

2023.02.11 - [Spring] - [Java/Spring] Resilience4j - Circuit Breaker (2). 설정

2023.02.13 - [Spring] - [Java/Spring] Resilience4j - Circuit Breaker (3). 적용