티스토리 뷰

Spring

[Spring Boot] CircuitBreaker Resilience4j

snail voyager 2023. 10. 7. 01:46
728x90
반응형

Resilience4j

자바 기반의 강력한 회복성 및 내결함성 라이브러리로, 분산 시스템 및 서비스에서 오류와 장애에 대응하는 데 사용

 Resilience4j는 멀티 스레딩 환경에서 안전하게 동작하며, 고성능을 제공

 

  • 서킷 브레이커 (Circuit Breaker): 서킷 브레이커 패턴을 구현하여 서비스 호출 중에 장애가 발생할 경우 재시도 및 회복을 관리합니다. 서킷이 열린 상태에서 호출을 차단하고, 특정 시간 동안 호출을 시도하지 않도록 설정할 수 있습니다.
  • 한도 지정 (Rate Limiting): 요청을 한도 내에서 제한하고 초과하는 경우 요청을 거부하거나 대기시킬 수 있습니다.
  • 반환 값 및 예외 처리 (Response and Exception Handling): 성공과 실패에 대한 반환 값을 처리하고, 실패 시 예외 처리를 사용자 정의할 수 있습니다.
  • Retry 메커니즘: 서비스 호출이 실패한 경우 재시도 기능을 제공합니다. 재시도 횟수 및 재시도 간격을 설정할 수 있습니다.
  • 메트릭과 모니터링: Resilience4j는 호출된 메소드의 성능 및 실패에 대한 메트릭을 수집하고, 이러한 메트릭을 모니터링 도구로 내보낼 수 있도록 지원합니다.
  • 자동 데코레이터 (Auto-Decorators): 자동 데코레이터를 사용하여 Resilience4j 기능을 기존 코드에 쉽게 통합할 수 있습니다.
  • 상태 및 구성 관리: Resilience4j는 YAML 또는 프로퍼티 파일을 사용하여 서비스의 동작을 구성할 수 있습니다.

build.gradle

dependencies {
	implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
}

application.yml

spring:
  cloud:
    openfeign:
      circuitbreaker:
        enabled: true
        alphanumeric-ids: #circuitBreaker 인스턴스를 구분하는 name을 알파벳과 숫자로 설정 여부
          enabled: true
resilience4j:
  circuitbreaker:
  	default:
    	minimumNumberOfCalls: 100
    instances:
      DemoClientgetDemo:
        minimumNumberOfCalls: 69
  timelimiter:
    instances:
      DemoClientgetDemo:
        timeoutDuration: 10s

minimumNumberOfCalls Test

@SpringBootTest
@AutoConfigureWireMock(port = 0)
@TestPropertySource(properties = {
        "naver.openapi-url=http://localhost:${wiremock.server.port}",
        "resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=10",	//설정 변경
})
@ExtendWith(SpringExtension.class)
class FeignClientWireMockTest {
    @Autowired
    FeignClient feignClient;
    
    @Test
    void searchLocalResilience4jTest_whenServerError_thenCallFallbackFactory() {
        WireMock.stubFor(WireMock.get(WireMock.urlPathEqualTo("/v1/search/local.json"))
                .willReturn(WireMock.aResponse().withStatus(HttpStatus.INTERNAL_SERVER_ERROR_500)));

        var search = new SearchLocalReq();
        search.setQuery("갈비집");

        for (int i=0; i<20; i++) {
            var result = feignClient.searchLocal(search);      //10번째까지 500서버 에러로 FeignException, 11번째부터는 circuit breaker open 으로 CallNotPermittedException
            assertThat(result).isNotNull();
            assertThat(result.getTotal()).isEqualTo(0);
        }
    }
}

slowCallDurationThreshold Test

@SpringBootTest
@AutoConfigureWireMock(port = 0)
@TestPropertySource(properties = {
        "naver.openapi-url=http://localhost:${wiremock.server.port}",
        "resilience4j.circuitbreaker.configs.default.slowCallDurationThreshold=3s"	//설정 변경
})
@ExtendWith(SpringExtension.class)
class FeignClientWireMockTest {
    @Autowired
    FeignClient feignClient;
    
    @Test
    void searchLocalResilience4jTest_whenServerDelay_thenTimeout() {
        WireMock.stubFor(WireMock.get(WireMock.urlPathEqualTo("/v1/search/local.json"))
                .willReturn(WireMock.aResponse()
                        .withStatus(HttpStatus.OK_200)
                        .withFixedDelay(4000)       //slowCallDurationThreshold 3초 설정으로 slow 카운팅
                        .withHeader("Content-Type", "application/json")
                        .withBody("{\n" +
                                "\t\"lastBuildDate\":\"Sun, 01 Oct 2023 23:31:58 +0900\",\n" +
                                "\t\"total\":1,\n" +
                                "\t\"start\":1,\n" +
                                "\t\"display\":1,\n" +
                                "\t\"items\":[\n" +
                                "\t\t{\n" +
                                "\t\t\t\"title\":\"몽탄2\",\n" +
                                "\t\t\t\"link\":\"http:\\/\\/www.mongtan.co.kr\",\n" +
                                "\t\t\t\"category\":\"한식>육류,고기요리\",\n" +
                                "\t\t\t\"description\":\"\",\n" +
                                "\t\t\t\"telephone\":\"\",\n" +
                                "\t\t\t\"address\":\"서울특별시 용산구 한강로1가 251-1\",\n" +
                                "\t\t\t\"roadAddress\":\"서울특별시 용산구 백범로99길 50\",\n" +
                                "\t\t\t\"mapx\":\"1269722500\",\n" +
                                "\t\t\t\"mapy\":\"375360103\"\n" +
                                "\t\t}\n" +
                                "\t]\n" +
                                "}")));

        var search = new SearchLocalReq();
        search.setQuery("갈비집");

        for (int i=0; i<20; i++) {
            var result = feignClient.searchLocal(search);

            if (i < 10) {   //10번째까지는 circuit breaker closed
                assertThat(result).isNotNull();
                assertThat(result.getTotal()).isEqualTo(1);
            } else {    //11번째부터 circuit breaker open
                assertThat(result).isNotNull();
                assertThat(result.getTotal()).isEqualTo(0);
            }
        }
    }
}

 

 

 

https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign-circuitbreaker

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

728x90
반응형
반응형
300x250