Resilience4j - Retry, fallbackMethod, Circuit Breaker

shlee ⚡️·2023년 11월 17일
0

resilience4j 문서

포트 구성

  • 없음 Currency Exchange Microservice에 resilience4j를 사용 할 것임

목표

  • 에러시에 재시도
  • 대체 방법 (대체 메소드)
  • circuit breaker 기능 다루기
  • Rate Limiting과 Bulkhead

currency-exchange-service에서 작업

01. 에러시 재시도

  • Currency Exchange Microservice에 resilience4j 사용

pom.xml

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>io.github.resilience4j</groupId>
			<artifactId>resilience4j-spring-boot3</artifactId>
		</dependency>

CircuitBreakerController - 생성

  • 간단한 api 생성
@RestController
public class CircuitBreakerController {
	
	@GetMapping("/sample-api")
	public String sampleApi() {
		return "Sample Api";
	}
}

CircuitBreakerController - 수정

  • 일부러 오류가 나도록 수정
  • @Retry default를 설정하고 로그를 출력해서 요청 실패시 몇 번 재시도를 하는지 확인
@RestController
public class CircuitBreakerController {
	private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);
	
	@GetMapping("/sample-api") 
	@Retry(name = "default") // 추가
	public String sampleApi() {
		logger.info("Sample Api call received");
		
		ResponseEntity<String> forEntity = 
				new RestTemplate().getForEntity("http://localhost:8080/some-dummy-url", String.class); // 추가
		return forEntity.getBody(); // 수정
	}
}

세번 재시도를 하고 나서야 오류를 리턴한다

CircuitBreakerController - 수정

  • @Retry 의 이름을 우리가 직접 설정하고 재시도 횟수도 바꿀 수 있다.
@RestController
public class CircuitBreakerController {
	private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);
	
	@GetMapping("/sample-api") 
	@Retry(name = "sample-api") // 수정
	public String sampleApi() {
		logger.info("Sample Api call received");
		
		ResponseEntity<String> forEntity = 
				new RestTemplate().getForEntity("http://localhost:8080/some-dummy-url", String.class);
		return forEntity.getBody();
	}
}

application.properties - Exchange Microservice

  • resilience4j.retry.instances.sample-api.maxAttempts=5 // 추가
resilience4j.retry.instances.sample-api.maxAttempts=5

02. 에러시에 대체 메소드

CircuitBreakerController - 수정

  • fallbackMethod = "hardcodedResponse" 대체 메소드 추가
@RestController
public class CircuitBreakerController {
	private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);
	
	@GetMapping("/sample-api") //
//	@Retry(name = "default")
	@Retry(name = "sample-api", fallbackMethod = "hardcodedResponse") //추가
	public String sampleApi() {
		logger.info("Sample Api call received");
		
		ResponseEntity<String> forEntity = 
				new RestTemplate().getForEntity("http://localhost:8080/some-dummy-url", String.class);
		return forEntity.getBody();
	}
	
	public String hardcodedResponse(Exception ex) { //추가
		return "fallback-response";
	}
}

application.properties

  • resilience4j.retry.instances.sample-api.waitDuration=1s 추가
  • 재시도 요청 인터벌을 1초간 줌
resilience4j.retry.instances.sample-api.waitDuration=1s

우리는 첫 시도 포함 총 5번의 재시도를 설정했으니
첫 요청 0초 + 4번의 요청 4초 해서 대략 4초가 걸린 것을 볼 수있다.

application.properties

  • resilience4jresilience4j.retry.instances.sample-api.enableExponentialBackoff=true 추가
  • 재시도 요청 시 ExponentialBackoff - 기하급수적 백 오프(재시도 할 때마다 요청의 인터벌이 조금씩 늘어남)
resilience4j.retry.instances.sample-api.enableExponentialBackoff=true

우리는 첫 시도 포함 총 5번의 재시도를 설정했으니
첫 요청 0초
두 번째 요청 1.007초
세 번째 요청 1.511초
네 번째 요청 2.26초
다섯 번째 요청 3.379초
도합 약 8초간의 시간이 걸림

  • 재요청 시 재시도의 인터벌이 조금씩 더 늘어남 (이게 ExponentialBackoff - 기하급수적 백 오프라고 한다)

03. Circuit breaker 기능 다루기

CircuitBreakerController - 수정

  • @CircuitBreaker로 수정
@RestController
public class CircuitBreakerController {
	private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);
	
	@GetMapping("/sample-api")
	@CircuitBreaker(name = "default", fallbackMethod = "hardcodedResponse") // 수정
	public String sampleApi() {
		logger.info("Sample Api call received");
		
		ResponseEntity<String> forEntity = 
				new RestTemplate().getForEntity("http://localhost:8080/some-dummy-url", String.class);
		return forEntity.getBody();
	}
	
	public String hardcodedResponse(Exception ex) {
		return "fallback-response";
	}
}



1초당 10번 호출 실행 명령

watch -n 0.1 curl http://localhost:8000/sample-api

윈도우는 powershell에서 이 명령 실행

while ($true) {
    Invoke-WebRequest -Uri "http://localhost:8000/sample-api"
    Start-Sleep -Seconds 0.1
}


문서에서 circuit breaker를 클릭하면

회로 차단기 상태가 여러 가지인 걸 볼 수 있어요

회로 차단기는 3개 서클에 걸쳐 폐쇄(closed), 개방(open), 반쯤 개방(half-open)으로 되어있죠

어떤 상태죠?

closed란 dependent 마이크로서비스에 계속 call하는 거예요

closed 상태에서는 항상 dependent 마이크로서비스에 call 할 거예요

open상태에서는 circuit breaker가 마이크로서비스를 호출하지 않아요

fallback으로 바로 응답할 거예요

half-open상태에서는 circuit breaker가

요청 비율을 해당 마이크로서비스로 보냅니다

나머지 요청에 대해서는 하드 코드화된 응답이나 대체 응답을 반환하죠

circuit breaker의 상태가 언제 바뀌죠?

예를 들어 회로 차단기는 closed상태예요

어플리케이션을 시작하면 circuit breaker는 보통 closed상태에 있어요

dependent 마이크로서비스에 1만 번 call해 보면

전부 안 되거나 90%가 안 돼요

그런 시나리오라면

회로 차단기가 open 상태로 바뀌겠죠

open 상태로 전환하면

잠시 기다리죠 구성할 수 있는 대기 시간이 있어요 (after wait duration)

그 후 circuit breaker가

half-open 상태로 바뀌죠

half-open 동안에는 circuit breaker가 마이크로서비스가 정상인지 확인할 거예요

요청의 퍼센트를 보내죠

퍼센트를 설정할 수 있어요

요청의 10%나 20%를 dependent 마이크로서비스에 보내죠

그에 대한 적절한 반응이 나오면

closed 상태로 돌아가게 되죠

적절한 반응을 얻지 못하면

open 상태로 돌아가게 되죠

지금 보시는 것처럼요 ↓

마지막 몇 분 동안 계속 요청을 실행하고 있죠

어플리케이션램 실행 때 circuit breaker가

마이크로서비스에 많은 요청을 한 걸 볼 수 있죠

일정 횟수의 오류 후에 회로 차단기가 open 상태로 전환됐어요

open 상태가 되면 두 로그 인터벌 사이에 틈이 있는 게 보이죠 (약 1분)

거의 1분의 격차가 있고 그 이후에 요청이 줄어드는 걸 보실 수 있어요

분당 10개 정도 요청하는 걸 보실 수 있죠

서비스가 백업됐는지 확인하죠

서비스가 백업되면 closed 상태로 돌아가죠

하지만 우리 경우엔 서비스가 백업되지 않을 거예요

1분만 더 기다렸다가 다시 시도하는 거예요

6분 18초 쯤에 다시 몇 가지 요청을 하는 걸 보실 수 있죠

문서에서 스크롤을 더 내리면

이 circuit breaker에서 광범위하게 설정할 수 있는 게 보여요

FailureRateThreshold는 실패율 임계값을 백분율로 구성하죠

실패율이 임계값보다 크거나 같을 때

circuit breaker가 open되고

short-circuiting가 시작돼요 (짧은 회로 차단)

slowCallDurationThreshold도 설정할 수 있어요

60초가 기본 값이고 (나도 1분 걸렸으니까)

여러분 고유의 값을 구성할 수 있죠

60초 이상 걸리는 호출은 느린 통화로 간주해요

기본값으로 호출이 100% 느려지면

회로가 다시 open되죠

보다시피 구성이 광범위해요

여기서 가능한 구성이죠

시간을 들여서 가능한 모든 다양한

구성에 대해 살펴보시길 권해드리고 싶어요

Spring Boot을 어떻게 설정하는지

보고 싶다면 Spring Boot Getting Started 문서를 살펴보는 것이 가장 좋아요.

여기로 와서 구성할 수 있는

모든 세부 사항을 볼 수 있어요

이게 실제로 구성하는 방법이에요

yaml 구성을 사용하지 않아요 사실 property 기반 구성을 사용하죠

property 기반 구성에서 이런 것도 할 수 있어요

퍼센트를 사용자 지정하는 경우

application.properties

resilience4j.circuitbreaker.instances.default.failureRateThreshold=90 
// yaml로 되어있으니 우리는 이런식으로 사용

실패율 임계값을 90%로 증가시키는 겁니다

요청의 90%가 실패하는 경우에만요

그럼 open 상태로 바꾸고요

그런 걸 특정 방식으로 구성할 수 있어요

04. Rate Limiting과 Bulkhead

Rate Limiting

  • 예를들면 10초동안 10,000번의 호출만 허용하겠다 이런식으로 설정하는 것을 Rate Limiting이라함 (비율 제한)

application.properties

  • 10초동안 4번의 요청만 허용
resilience4j.ratelimiter.instances.default.limitRefreshPeriod=10s
resilience4j.ratelimiter.instances.default.limitForPeriod=4
// resilience4j.ratelimiter.instances.sample-api.limitForPeriod=4
// resilience4j.ratelimiter.instances.sample-api.limitForPeriod=4

CircuitBreakerController

@RestController
public class CircuitBreakerController {
	private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);
	
	@GetMapping("/sample-api") 
	//@RateLimiter(name = "sample-api") // 추가
	@RateLimiter(name = "default") // 추가
	public String sampleApi() {
		logger.info("Sample Api call received");
		
		return "sample-api";
	}
	
}

Bulkhead

  • 동시 호출이 얼만큼 허용되는지도 설정할 수 있죠 Bulkhead에요

application.properties

  • 동시호출 수 최대 10개까지만 허용
resilience4j.bulkhead.instances.default.maxConcurrentCalls=10
// resilience4j.bulkhead.instances.sample-api.maxConcurrentCalls=10

CircuitBreakerController

@RestController
public class CircuitBreakerController {
	private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);
	
	@GetMapping("/sample-api") 
	//@Bulkhead(name = "sample-api") // 추가
	@Bulkhead(name = "default") // 추가
	public String sampleApi() {
		logger.info("Sample Api call received");
		
		return "sample-api";
	}
	
}
profile
흔들리지 말고.. 묵묵히

0개의 댓글

관련 채용 정보