<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>
@RestController
public class CircuitBreakerController {
@GetMapping("/sample-api")
public String sampleApi() {
return "Sample Api";
}
}
@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(); // 수정
}
}
세번 재시도를 하고 나서야 오류를 리턴한다
@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();
}
}
resilience4j.retry.instances.sample-api.maxAttempts=5
@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";
}
}
resilience4j.retry.instances.sample-api.waitDuration=1s
우리는 첫 시도 포함 총 5번의 재시도를 설정했으니
첫 요청 0초 + 4번의 요청 4초 해서 대략 4초가 걸린 것을 볼 수있다.
resilience4j.retry.instances.sample-api.enableExponentialBackoff=true
우리는 첫 시도 포함 총 5번의 재시도를 설정했으니
첫 요청 0초
두 번째 요청 1.007초
세 번째 요청 1.511초
네 번째 요청 2.26초
다섯 번째 요청 3.379초
도합 약 8초간의 시간이 걸림
@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";
}
}
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
}
회로 차단기 상태가 여러 가지인 걸 볼 수 있어요
회로 차단기는 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 기반 구성에서 이런 것도 할 수 있어요
퍼센트를 사용자 지정하는 경우
resilience4j.circuitbreaker.instances.default.failureRateThreshold=90
// yaml로 되어있으니 우리는 이런식으로 사용
실패율 임계값을 90%로 증가시키는 겁니다
요청의 90%가 실패하는 경우에만요
그럼 open 상태로 바꾸고요
그런 걸 특정 방식으로 구성할 수 있어요
Rate Limiting
이라함 (비율 제한)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
@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";
}
}
resilience4j.bulkhead.instances.default.maxConcurrentCalls=10
// resilience4j.bulkhead.instances.sample-api.maxConcurrentCalls=10
@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";
}
}