이번 블로그에서는 스프링 클라우드 서킷브레이커를 중심으로, 그 개념과 사용법, 실제 적용 예제까지 상세히 알아보겠습니다.
자세한 내용은 GIT-circuit-breaker 브랜치에서 알아보실 수 있습니다.
서킷브레이커 패턴은 네트워크 호출에서 발생하는 잠재적인 장애를 격리하고, 전체 시스템으로의 영향을 최소화하기 위해 고안된 패턴입니다.
스프링 클라우드 서킷브레이커는 다양한 서킷브레이커 라이브러리를 통합하고, 이를 쉽게 설정하고 사용할 수 있는 기능을 제공합니다. 대표적으로 Resilience4j와 Hystrix를 지원합니다.
더이상의 기능추가가 없는 Hystrix가 아닌 Resilience4j에 대해 정리하고 실습을 진행하곘습니다.
앞서 MSA에서 진행중인 프로젝트중 product에서 추가 구현을 진행하겠습니다.
이전 글이 궁금하시다면 [MSA알아보기] 클라이언트 사이드 로드 밸런싱(FeignClient와 Ribbon)를 확인해주시면 감사하겠습니다.
dependencies {
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-aop'
}
위와 같이 resilence4j와 apo를 추가해주시면 됩니다.
resilience4j:
circuitbreaker:
configs:
default: # 기본 구성 이름
registerHealthIndicator: true # 애플리케이션의 헬스 체크에 서킷 브레이커 상태를 추가하여 모니터링 가능
# 서킷 브레이커가 동작할 때 사용할 슬라이딩 윈도우의 타입을 설정
# COUNT_BASED: 마지막 N번의 호출 결과를 기반으로 상태를 결정
# TIME_BASED: 마지막 N초 동안의 호출 결과를 기반으로 상태를 결정
slidingWindowType: COUNT_BASED # 슬라이딩 윈도우의 타입을 호출 수 기반(COUNT_BASED)으로 설정
# 슬라이딩 윈도우의 크기를 설정
# COUNT_BASED일 경우: 최근 N번의 호출을 저장
# TIME_BASED일 경우: 최근 N초 동안의 호출을 저장
slidingWindowSize: 5 # 슬라이딩 윈도우의 크기를 5번의 호출로 설정
minimumNumberOfCalls: 5 # 서킷 브레이커가 동작하기 위해 필요한 최소한의 호출 수를 5로 설정
slowCallRateThreshold: 100 # 느린 호출의 비율이 이 임계값(100%)을 초과하면 서킷 브레이커가 동작
slowCallDurationThreshold: 60000 # 느린 호출의 기준 시간(밀리초)으로, 60초 이상 걸리면 느린 호출로 간주
failureRateThreshold: 50 # 실패율이 이 임계값(50%)을 초과하면 서킷 브레이커가 동작
permittedNumberOfCallsInHalfOpenState: 3 # 서킷 브레이커가 Half-open 상태에서 허용하는 최대 호출 수를 3으로 설정
# 서킷 브레이커가 Open 상태에서 Half-open 상태로 전환되기 전에 기다리는 시간
waitDurationInOpenState: 20s # Open 상태에서 Half-open 상태로 전환되기 전에 대기하는 시간을 20초로 설정
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
private String id;
private String title;
}
@RestController
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable("id") String id) {
return productService.getProductDetails(id);
}
}
@Service
@RequiredArgsConstructor
public class ProductService {
private final Logger log = LoggerFactory.getLogger(getClass());
private final CircuitBreakerRegistry circuitBreakerRegistry;
@PostConstruct
public void registerEventListener() {
circuitBreakerRegistry.circuitBreaker("productService").getEventPublisher()
.onStateTransition(event -> log.info("#######CircuitBreaker State Transition: {}", event)) // 상태 전환 이벤트 리스너
.onFailureRateExceeded(event -> log.info("#######CircuitBreaker Failure Rate Exceeded: {}", event)) // 실패율 초과 이벤트 리스너
.onCallNotPermitted(event -> log.info("#######CircuitBreaker Call Not Permitted: {}", event)) // 호출 차단 이벤트 리스너
.onError(event -> log.info("#######CircuitBreaker Error: {}", event)); // 오류 발생 이벤트 리스너
}
@CircuitBreaker(name = "productService", fallbackMethod = "fallbackGetProductDetails")
public Product getProductDetails(String productId) {
log.info("###Fetching product details for productId: {}", productId);
if ("111".equals(productId)) {
log.warn("###Received empty body for productId: {}", productId);
throw new RuntimeException("Empty response body");
}
return new Product(
productId,
"Sample Product"
);
}
public Product fallbackGetProductDetails(String productId, Throwable t) {
log.error("####Fallback triggered for productId: {} due to: {}", productId, t.getMessage());
return new Product(
productId,
"Fallback Product"
);
}
}
여기서 확인 가능한 것은
1. 에러가 지속적으로 발생이 되면 CLOSED -> OPEN 상태로 변경이 됩니다.
2. OPNE -> HALFOPEN 상태가 되기 위해 yml에서 설정한 20초정도가 걸리는것을 볼 수 있습니다3.
3. HALF_OPEN 상태에서는 여러번 호출을 하더라 3번으로 제한됩니다.
4. 에러가 없다고 판단되면 CLOSED로 변경되는 것을 볼 수 있습니다.
5. Open 상태가 되면 getProductDetails 함수를 타지않고 바로 fallbackGetProductDetails_ 로 호출 되는것을 확인 할 수 있습니다.
이상으로 구현을 마무리 하겠습니다.