서킷 브레이커는 마이크로서비스 간의 호출 실패를 감지하고 시스템의 전체적인 안정성을 유지하는 패턴입니다. 외부 서비스 호출 실패 시 빠른 실패를 통해 장애를 격리하고, 시스템의 다른 부분에 영향을 주지 않도록 합니다. 서킷 브레이커는 주로 세 가지 상태를 가집니다:
Resilience4j는 서킷 브레이커 라이브러리로, 서비스 간의 호출 실패를 감지하고 시스템의 안정성을 유지합니다. 다양한 서킷 브레이커 기능을 제공하며, 장애 격리 및 빠른 실패를 통해 복원력을 높입니다.
Resilience4j를 사용하려면 Spring Boot 애플리케이션에 의존성을 추가해야 합니다.
dependencies {
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
application.yml
파일에서 Resilience4j의 설정을 할 수 있습니다.
spring:
application:
name: sample
server:
port: 19090
resilience4j:
circuitbreaker:
configs:
default:
registerHealthIndicator: true
slidingWindowType: COUNT_BASED
slidingWindowSize: 5
minimumNumberOfCalls: 5
slowCallRateThreshold: 100
slowCallDurationThreshold: 60000
failureRateThreshold: 50
permittedNumberOfCallsInHalfOpenState: 3
waitDurationInOpenState: 20s
management:
endpoints:
web:
exposure:
include: prometheus
prometheus:
metrics:
export:
enabled: true
Fallback 메서드는 외부 서비스 호출이 실패했을 때 대체 로직을 제공하는 메서드입니다.
@Service
public class MyService {
@CircuitBreaker(name = "myService", fallbackMethod = "fallbackMethod")
public String myMethod() {
return externalService.call();
}
public String fallbackMethod(Throwable t) {
return "Fallback response";
}
}
Resilience4j Dashboard를 사용하여 서킷 브레이커의 상태를 모니터링할 수 있습니다.
dependencies {
implementation 'io.github.resilience4j:resilience4j-micrometer'
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
application.yml
파일에서 Prometheus 설정을 추가합니다.
management:
endpoints:
web:
exposure:
include: prometheus
prometheus:
metrics:
export:
enabled: true
Prometheus와 Grafana를 사용하여 Resilience4j 서킷 브레이커의 상태를 실시간으로 모니터링할 수 있습니다. Prometheus를 통해 수집된 메트릭을 Grafana 대시보드에서 시각화할 수 있습니다.
이번 실습에서는 유레카를 사용하지 않고, 프로젝트에서는 상품을 조회하는 것을 가정합니다. 상품 아이디 111을 호출하면 에러를 발생시켜 fallbackMethod
를 실행하는 것을 확인합니다. 또한 이벤트리스너를 사용하여 서킷 브레이커의 상태를 조회해보겠습니다.
Start Spring에서 프로젝트를 생성합니다. 디펜던시는 위 설정을 참고합니다. 아래는 각 파일의 코드 예시입니다.
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
private String id;
private String title;
}
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable("id") String id) {
return productService.getProductDetails(id);
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
@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");
}
}
http://localhost:19090/products/11
을 3번 호출합니다.http://localhost:19090/products/111
을 여러 번 호출하면서 서킷 브레이커의 상태가 변경되는 것을 확인합니다.getProductDetails
함수가 호출되지 않고 바로 fallbackGetProductDetails
로 호출되는 것을 확인할 수 있습니다.http://localhost:19090/actuator/prometheus
로 접속하면 인스턴스의 상태 값들을 확인할 수 있습니다.이로써 Resilience4j를 이용한 서킷 브레이커 설정 및 활용에 대한 개요와 실습을 마칩니다. 이를 통해 시스템의 안정성과 복원력을 높일 수 있는 방법을 배울 수 있었습니다.