[Spring] Spring-Cloud를 활용한 MSA 설계(1) - 서비스 디스커버리, 로드 밸런싱
[Spring] Spring-Cloud를 활용한 MSA 설계(0) - IntroSpring-Cloud를 통해 MSA 설계를 하기에 앞서,MSA의 개념과 Spring-Cloud의 주요 모듈 및 구성 요소를 정리한 서론입니다. ✅ MSA란?MSA(Micro Services Architecture)
zapzook.tistory.com
지난 포스팅에서 다뤘던 서비스 디스커버리, 로드 밸런싱에 이어서
이번 포스팅에선 서킷 브레이커에 대해 자세히 알아보고, 실습을 진행할 예정이다.
✅ 서킷 브레이커 (Resilience4j)
서킷 브레이커는 마이크로 서비스간의 호출 실패를 감지하고 시스템의 전체적인 안정성을 유지하는 패턴이다.
외부 서비스 호출 실패 시 빠른 실패를 통해 장애를 격리하고, 시스템의 다른 부분에 영향을 주지 않도록 한다.
예를 들어서 주문 서비스와 결제 서비스로 나뉜 마이크로 서비스가 있다고 하자.
주문 서비스가 결제 서비스를 호출할 때 결제 서비스가 다운되거나 느린 경우, 서킷 브레이커가 이를 감지하고 더 이상의 호출을 차단한다. 이로 인해 주문 서비스가 무한 대기하거나 장애 전파로 인해 다운되는 것을 방지할 수 있다.
📌 Reslilence4j
Resilience4j는 서킷 브레이커 라이브러리로, 서비스 간의 호출 실패를 감지하고 시스템의 안정성을 유지한다.
다양한 서킷 브레이커 기능을 제공하며, 장애 격리 및 빠른 실패를 통해 복원력을 높인다.
주요 특징
- 서킷 브레이커 상태 : closed, open, half-open << 세 가지 상태를 통해 호출 실패를 관리한다
- Fallback: 호출 실패 시 대체 로직을 제공하여 시스템 안정성을 확보한다
- 모니터링 : 서킷 브레이커 상태를 모니터링하고 관리할 수 있는 다양한 도구를 제공한다
Fallback? Failback?
서킷 브레이커를 공부할 때, 어디서는 Fallback이라고 말하고 어디서는 Failback이라고 말한다...
Fallback : 서비스 호출 실패 시, 대체 방법을 제공 (예: "결제 서비스가 불가능합니다.")
Failback : 서비스가 복구되면, 원래 경로로 돌아가는 것
즉, 서킷 브레이커에서 사용하는 정확한 용어는 Fallback이 맞다!
📌 Fallback 매커니즘
Fallback 메서드는 외부 서비스 호출이 실패했을 때 대체 로직을 제공하는 메서드이다.
장점으로는 시스템의 안정성을 높이고, 장애가 발생해도 사용자에게 일정한 응답을 제공할 수 있다.
또, 장애가 다른 서비스에 전파되는 것을 방지할 수 있다.
강사분이 설명하시길 에러를 에러가 아닌 것처럼 고객에게 보여줄 수 있다는 부분에서 이점이 크다고 한다..
📌 Resilience4j 대시보드 설정
build.gradle
dependencies {
implementation 'io.github.resilience4j:resilience4j-micrometer'
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
application.yml
management:
endpoints:
web:
exposure:
include: prometheus
prometheus:
metrics:
export:
enabled: true
위와 같이 설정을 해주면 서킷 브레이커의 상태를 모니터링 할 수 있다.
localhost:{port}/actuator/prometheus 에 접속하면 Prometheus를 통해 수집된 메트릭을 확인할 수 있다.
이후 수집된 메트릭을 Grafana 대시보드를 통해 시각화 할 수 있는데, 이 부분은 본 포스팅에선 다루지 않을 예정이다.
그냥 이런게 있고 가능하다는 것만 알아두자
✅ 서킷 브레이커 (Resilience4j) 실습
이번 실습에서는 Eureka는 사용하지 않고, 서킷 브레이커의 fallbackMethod를 확인하는데 중점을 두겠다.
또, 이벤트 리스터를 사용해 서킷브레이커의 상태를 조회해볼 예정이다.
📌 build.gradle
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'
}
tasks.named('test'){
useJUnitPlatform()
}
resilience4j 사용을 위해 디펜던시를 추가해준다.
단, Spring starter에서 디펜던시를 추가하면 추상화 계층의 Resilience가 사용되기에
꼭 명시된 디펜던시를 추가해줘야 한다.
📌 Product.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
private String id;
private String title;
}
📌 ProductController.java
@RestController
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable("id") String id) {
return productService.getProductDetails(id);
}
}
📌 ProductService.java
@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"
);
}
// 이벤트 설명 표
// +---------------------------+-------------------------------------------------+--------------------------------------------+
// | 이벤트 | 설명 | 로그 출력 |
// +---------------------------+-------------------------------------------------+--------------------------------------------+
// | 상태 전환 (Closed -> Open) | 연속된 실패로 인해 서킷 브레이커가 오픈 상태로 전환되면 발생 | CircuitBreaker State Transition: ... |
// | 실패율 초과 | 설정된 실패율 임계치를 초과하면 발생 | CircuitBreaker Failure Rate Exceeded: ... |
// | 호출 차단 | 서킷 브레이커가 오픈 상태일 때 호출이 차단되면 발생 | CircuitBreaker Call Not Permitted: ... |
// | 오류 발생 | 서킷 브레이커 내부에서 호출이 실패하면 발생 | CircuitBreaker Error: ... |
// +---------------------------+-------------------------------------------------+--------------------------------------------+
// +------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+
// | 이벤트 | 설명 | 로그 출력 |
// +------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+
// | 메서드 호출 | 제품 정보를 얻기 위해 메서드를 호출 | ###Fetching product details for productId: ... |
// | (성공 시) 서킷 브레이커 내부에서 호출 성공 | 메서드 호출이 성공하여 정상적인 응답을 반환 | |
// | (실패 시) 서킷 브레이커 내부에서 호출 실패 | 메서드 호출이 실패하여 예외가 발생 | #######CircuitBreaker Error: ... |
// | (실패 시) 실패 횟수 증가 | 서킷 브레이커가 실패 횟수를 증가시킴 | |
// | (실패율 초과 시) 실패율 초과 | 설정된 실패율 임계치를 초과하면 발생 | #######CircuitBreaker Failure Rate Exceeded: ... |
// | (실패율 초과 시) 상태 전환 (Closed -> Open) | 연속된 실패로 인해 서킷 브레이커가 오픈 상태로 전환됨 | #######CircuitBreaker State Transition: Closed -> Open at ... |
// | (오픈 상태 시) 호출 차단 | 서킷 브레이커가 오픈 상태일 때 호출이 차단됨 | #######CircuitBreaker Call Not Permitted: ... |
// | (오픈 상태 시) 폴백 메서드 호출 | 메서드 호출이 차단될 경우 폴백 메서드 호출 | ####Fallback triggered for productId: ... due to: ... |
// +------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+
}
상품 번호 111이 입력되면 서킷 브레이커를 통해 fallback 메서드가 실행되도록 구성하였다.
📌 application.yml
spring:
application:
name: sample
server:
port: 19090
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초로 설정
management:
endpoints:
web:
exposure:
include: prometheus
prometheus:
metrics:
export:
enabled: true
위 내용과 같이 서킷 브레이커의 세부 설정을 조정할 수 있다.
이는 서비스의 규모나 요구사항에 따라 유동적으로 달라질 수 있다.
📌 Run
상품 번호 111을 호출하여 서킷 브레이커가 Open 상태가 되면 getProductDetails 함수를 타지 않고
바로 fallbackGetProductDetails 로 호출 되는 것을 확인할 수 있다.
이후 localhost:{port}/actuator/prometheus 로 접속하면 인스턴스의 상태값들을 확인할 수 있다.
이렇게 보면 가독성이 굉장히 떨어지는데, 이 때문에 Grafana라는 시각화 툴이 존재하는 것
'Study > Spring' 카테고리의 다른 글
Intellij에서 공공 데이터셋(csv) 동기화 및 활용하기 (0) | 2025.02.17 |
---|---|
[Spring] Spring-Cloud를 활용한 MSA 설계(1) - 서비스 디스커버리, 로드 밸런싱 (0) | 2025.02.12 |
[Spring] Spring-Cloud를 활용한 MSA 설계(0) - Intro (0) | 2025.02.11 |
[Spring][Redis] 동시성 이슈와 분산락을 이용한 동시성 제어 (0) | 2024.03.27 |
[Spring] AOP를 활용한 기능 모듈화 (0) | 2024.03.25 |