마이크로서비스 아키텍처(MSA)는 여러 독립적인 서비스가 상호작용하며 복잡한 시스템을 형성합니다.
이러한 아키텍처는 확장성과 유연성을 제공하지만, 동시에 서비스 간의 의존성이 증가하여 시스템의 안정성이 위협받을 수 있습니다.
특히, 하나의 서비스가 실패하면 연쇄적으로 다른 서비스에 영향을 미치고, 이로 인해 전체 시스템의 신뢰성이 떨어질 수 있습니다.
이러한 문제를 해결하기 위해 서킷 브레이커 패턴이 도입되었습니다.이번 포스팅에서는 제가 공부한 내용을 바탕으로 서킷 브레이커의 역할과 필요성에 대해 정리하고, 서킷 브레이커의 주요 기능과 동작확인을 해보겠습니다.
서킷 브레이커(Circuit Breaker)란❓
서킷 브레이커(Circuit Break)를 번역해 보면, "회로 차단기"입니다. SpringCloud에서의 서킷 브레이커 역시 이 회로 차단기의 역할과 비슷합니다.
서킷 브레이커는 마이크로서비스 간의 호출 실패를 감지하고 시스템의 전체적인 안정성을 유지하는 패턴으로 서비스 간의 장애가 전체 시스템으로 확산되는 것을 방지하고, 서비스의 회복 가능성을 높이는 역할을 합니다.
이 패턴은 장애가 발생했을 때 시스템이 자동으로 대응하고, 문제를 조기에 감지하여 시스템 전체의 안정성을 유지하는 데 도움을 줍니다.
따라서 서킷 브레이커를 활용하면 장애 상황에서도 서비스가 신뢰성을 유지할 수 있으며, 사용자는 서비스의 지속적인 가용성을 경험할 수 있습니다.
이러한 서킷 브레이커의 기능 위해 설계된 Resilience4j를 알아보기에 앞서 먼저 서킷 브레이커의 상태에 대해서 알아야 합니다.
서킷 브레이커(Circuit Breaker)의 상태
1. 닫힘(Closed)
닫혀있다고 해서 일반적으로 생각하는 부정적인 느낌이 아니라 서킷 브레이커가 닫힌 상태에서는 모든 요청이 정상적으로 처리됩니다. 이 상태에서는 시스템이 정상적으로 작동하고 있으며, 서킷 브레이커가 장애를 감지하지 않은 상태라고 할 수 있습니다.
- 상황 : 서비스 호출이 정상적으로 이루어지고 있으며, 장애 발생률이 설정된 임계값 이하입니다.
- 상태 전환 : 장애율이 설정된 임계값을 초과하면 서킷 브레이커는 반열림(Half-Open) 상태로 전환됩니다.
2. 열림(Open)
마찬가지로 열려있다고 해서 일반적으로 생각하는 긍정적인 느낌이 아니라 서킷 브레이커가 열린 상태에서는 모든 요청이 실패하고, 대체 처리 로직이 호출됩니다. 이 상태는 시스템의 장애를 방지하고, 장애가 회복될 때까지 요청을 차단합니다.
- 상황 : 장애율이 임계값을 초과하여 시스템의 안정성이 위협받는 상황입니다.
- 상태 전환 : 일정 시간 후, 서킷 브레이커는 반열림(Half-Open) 상태로 전환됩니다.
3. 반열림(Half-Open)
서킷 브레이커가 반열림 상태에서는 일부 요청이 허용되며, 시스템의 복구 여부를 확인합니다. 이 상태는 시스템이 회복되었는지 테스트하는 단계입니다.
- 상황: 서킷 브레이커가 열린 상태에서 일정 시간이 지나면 반열림 상태로 전환됩니다. 이때 일부 요청을 통해 시스템의 상태를 모니터링합니다.
- 상태 전환
- 성공: 요청이 성공하면 서킷 브레이커는 닫힘(Closed) 상태로 전환되어 정상 운영을 계속합니다.
- 실패: 요청이 실패하면 서킷 브레이커는 다시 열림(Open) 상태로 전환되어, 장애가 해결될 때까지 요청을 차단합니다.
Resilience4j란❓
Resilience4j는 Netflix Hystrix로부터 영감을 받은 함수형 프로그래밍으로 설계된 경량의 장애 허용(fault tolerance) 라이브러리로, 서킷브레이커 패턴을 위해 사용됩니다.
주요 기능
- 서킷 브레이커 (Circuit Breaker)
- 장애를 감지하고 서비스의 상태에 따라 요청을 차단하거나 허용합니다.
- 서킷 브레이커는 Closed, Open, Half-Open 상태를 통해 장애를 관리합니다.
- 리트라이 (Retry)
- 실패한 요청을 자동으로 재시도합니다.
- 리트라이 전략을 사용하여 임시적인 장애나 네트워크 문제를 처리할 수 있습니다.
- Fallback
- 서비스 호출이 실패했을 때, 사전에 정의된 대체 응답을 제공하여 사용자에게 안정적인 서비스를 제공합니다.
- 장애 발생 시 대체 로직을 실행하거나 로컬 캐시, 백업 서비스 등을 활용하여 실패를 처리합니다.
- 타임아웃 (Timeout)
- 특정 시간 내에
응답을 받지 못하면요청을 실패로 간주합니다. - 타임아웃 설정을 통해 시스템 자원 낭비를 줄이고, 서비스 응답 속도를 보장합니다.
- 특정 시간 내에
- 버킷브리크 (Bulkhead)
- 서비스의 특정 부분이 실패하더라도
전체 시스템에 영향을 미치지 않도록 격리합니다. - 리소스를 분리하여 독립적으로 관리합니다.
- 서비스의 특정 부분이 실패하더라도
- 회로 차단기 (Rate Limiter)
- 요청의 수를 제한하여 시스템 과부하를 방지합니다.
- 요청 빈도를 조절하여 서버의 부하를 관리할 수 있습니다.
- 캐시 (Cache)
- 결과를 캐시 하여 동일한 요청에 대해 빠른 응답을 제공합니다.
- 데이터의 재사용을 통해 성능을 향상합니다.
Resilience4 j 설정
Resilience4j는 Spring Boot의 스타터 의존성으로 추가할 수 있습니다. 하지만 이 스타터만 추가하면 원하는 동작이 제대로 작동하지 않을 수 있습니다.
그 이유는 이 스타터가 기본적인 구현체만 제공하기 때문입니다.
실제로 Resilience4j를 효과적으로 사용하기 위해서는 GitHub에서 제공하는 Resilience4j 라이브러리를 Spring Boot 프로젝트에 추가해야 합니다.
1. 의존성 추가
dependencies {
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-aop'
}
2. application 파일 설정
Resilience4j의 설정은 application.yml이나 application.properties 파일에서 설정할 수 있습니다.
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초로 설정
@CircuitBreaker
@CircuitBreaker 어노테이션은 Resilience4j에서 제공하는 서킷 브레이커(Circuit Breaker) 기능을 사용하기 위해 사용하는 어노테이션입니다.
어노테이션 속성
name
- 서킷 브레이커의 이름을 지정합니다. 여러 서킷 브레이커를 사용할 경우 각기 다른 이름을 부여하여 구분할 수 있습니다.
fallbackMethod
- 장애 발생 시 호출할 대체 메서드의 이름을 지정합니다.
- 이 메서드는 원래 메서드와 동일한 파라미터를 받아야 하며, Throwable 타입의 인자를 받아야 합니다.
Fallback 메커니즘
Fallback 메커니즘은 Resilience4j의 중요한 기능 중 하나로, 애플리케이션의 장애나 예외 상황을 처리하는 데 도움을 줍니다.
Fallback 메서드는 외부 서비스 호출이 실패했을 때 대체 로직을 제공하는 메서드입니다.
@Service
public class MyService {
@CircuitBreaker(name = "myService", fallbackMethod = "fallbackMethod")
public String myMethod() {
// 외부 서비스 호출
return externalService.call();
}
public String fallbackMethod(Throwable t) {
return "Fallback response";
}
}
@CircuitBreaker 어노테이션
- name = "myService"
- 이 속성은 서킷 브레이커의 이름을 정의합니다.
fallbackMethod = "fallbackMethod"
- 이 속성은 실제 메서드가 실패할 경우 호출될 대체 메서드를 지정합니다.
- 대체 메서드는 원래 메서드와 동일한 반환형과 적절한 매개변수를 가져야 합니다.
myMethod()
- 외부 서비스 호출을 포함하는 메서드입니다.
- 이 메서드가 호출되면 Resilience4j의 서킷 브레이커가 작동하여, 장애가 발생할 경우 Fallback 메커니즘이 작동합니다.
fallbackMethod(Throwable t)
- myMethod() 호출이 실패할 경우 대체 로직을 제공하는 메서드입니다.
- 위 코드에서는 외부 서비스(call() 메서드)가 실패할 때 "Fallback response"를 반환합니다.
FallbackMethod의 역할
- 장애 전파 방지
- fallbackMethod는 서비스 호출 중 에러가 발생했을 때 대체 응답을 제공함으로써, 에러가 다른 서비스로 전파되는 것을 방지합니다.
- 이를 통해 장애가 전체 시스템으로
확산되지 않도록 합니다.
- 에러 원인 파악 용이
- 복잡한 서비스 호출 로직에서, 어떤 서비스에서 에러가 발생했는지 식별하기 어려운 경우가 있습니다.
- fallbackMethod는 특정 서비스 호출에서 발생한 에러를 한 곳에서 처리하므로, 에러를 효과적으로 관리할 수 있습니다.
- 에러 전파 차단
- 외부 서비스 호출 중 하나에서 문제가 발생하더라도, fallbackMethod를 사용하면
해당 에러를 다른 서비스로 전달하지 않고 자체적으로 처리할 수 있습니다. - 이는 에러의 전파를 차단하고, 나머지 서비스의 안정성을 유지하는 데 도움이 됩니다.
- 외부 서비스 호출 중 하나에서 문제가 발생하더라도, fallbackMethod를 사용하면
- 일관된 대체 응답 제공
- 여러 서비스 호출 중 하나가 실패했을 때, fallbackMethod를 통해 일관된 대체 응답을 제공할 수 있습니다.
- 이를 통해 사용자에게 안정적인 응답을 제공하고, 서비스의 일관성을 유지할 수 있습니다.
FallbackMethod의 장점
- 서비스 연속성 유지
- fallbackMethod를 사용하면 외부 서비스나 API 호출이 실패했을 때도 애플리케이션이 지속적으로 응답할 수 있습니다.
- 이를 통해 서비스 연속성을 유지할 수 있습니다.
- 예외 처리 간소화
- 복잡한 예외 처리를 fallbackMethod로 대체할 수 있어, 비즈니스 로직에서 예외 처리를 단순화할 수 있습니다.
- 이는 코드의 유지보수성을 높이는 데 도움이 됩니다.
- 사용자 경험 개선
- 시스템이 실패할 때 사용자에게 유용한 대체 응답을 제공함으로써 사용자 경험을 개선할 수 있습니다.
- 예를 들어, 사용자에게 친절한 에러 메시지나 기본 응답을 반환할 수 있습니다.
- 모니터링과 알림 설정
- fallbackMethod를 통해 문제가 발생했을 때 로그를 남기거나 알림을 설정하여 개발자나 운영팀이 문제를 인지하고 대응할 수 있게 합니다.
- 유연한 대체 응답 제공
- 특정 상황에 맞게 대체 응답을 유연하게 구성할 수 있습니다.
- 예를 들어, 비즈니스 로직에 맞는 기본값을 반환하거나, 실패 원인에 따른 맞춤형 응답을 제공할 수 있습니다.
Resilience4j와 Spring Cloud 연동
Resilience4j와 Spring Cloud 통합
Resilience4j는 Spring Cloud의 일부로, Eureka와 Ribbon과 같은 Spring Cloud 구성 요소와 쉽게 통합할 수 있습니다.
Eureka와 Ribbon관련 포스팅은 아래에서 확인 가능 합니다.▼
따라서, Eureka와 Ribbon과 통합을 통해 서비스 디스커버리와 로드 밸런싱 기능을 효과적으로 사용할 수 있습니다.
기본 설정
spring:
application:
name: my-service
cloud:
circuitbreaker:
resilience4j:
enabled: true
Spring Cloud와 Resilience4j를 통합하려면, application.yml 파일 또는 application.properties에 위와 같은 설정을 추가하여 Resilience4j를 활성화할 수 있습니다.
서킷브레이커 동작 확인
코드는 위와 같이 구성되어 있으며, Controller에서 지정한 API로 들어오면 getProduct() 메서드가 호출됩니다.
getProduct() 메서드는 ProductService로 요청을 전달하고, @PathVariable 어노테이션으로 받은 아이디(id)가 '111'인 경우에는 Fallback 메서드로 넘어가도록 로직을 구성했습니다.
@PathVariable 관련 포스팅▼
- localhost:19090/product/1 호출 시 정상 호출 되는 것을 확인할 수 있습니다.
- localhost:19090/product/2 호출 시 정상 호출 되는 것을 확인할 수 있습니다.
- localhost:19090/product/112 호출 시 정상 호출 되는 것을 확인할 수 있습니다.
- localhost:19090/product/111 호출 시 Fallback 메서드를 호출하여 "Fallback Product"라는 문자열이 반환된 것을 확인할 수 있습니다.
- localhost:19090/product/1111 호출 시 Fallback 메서드를 호출하여 "Fallback Product"라는 문자열이 반환된 것을 확인할 수 있습니다.
id가 "111"로 호출했을 때 fallback 메서드가 호출되고, 이후에 다른 id인 "1111"로 호출해도 여전히 fallback 메서드가 호출되는 이유는 "서킷 브레이커의 상태" 때문입니다.
앞서 알아본 대로, 서킷 브레이커의 상태가 직전에 id가 "111"로 호출했기 때문에 OPEN상태로 전환되었습니다.
따라서 "OPEN"으로 설정되어 있을 경우, 정상적인 서비스 호출(id = "1111")을 시도하더라도 항상 fallback 메서드가 호출됩니다.
- 마찬가지로 아직 "OPEN" 상태이기 때문에 localhost:19090/product/11122 호출 시 Fallback 메서드를 호출하여 "Fallback Product"라는 문자열이 반환된 것을 확인할 수 있습니다.
- localhost:19090/product/112 호출 시 정상 호출 되는 것을 확인할 수 있습니다.
위와 같이 Fallbck Method가 호출되고 일정 시간이 지난 후 서킷 브레이커는 "Half-Open"상태로 전환됩니다.
이 상태에서는 몇 개의 요청만을 테스트하여 서비스가 복구되었는지 확인합니다. 만약 테스트 요청(id = "11122")이 성공하면 서킷 브레이커는 다시 "CLOSED" 상태로 돌아가고, 실패하면 "OPEN" 상태로 돌아갑니다.
서킷브레이커 동작 및 로그 확인
@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"
);
}
서킷브레이커의 로그를 확인하기 위해서 서비스 파일에다가 코드를 추가했습니다.
마찬가지로 id가 "111"이 아닌 요청에 대해서는 정상적으로 호출이 되다가, "111"이 들어왔을 때 Fallback 메서드를 호출합니다.
id가 "111"호출을 계속해서 보내면 실패율이 미리 설정한 60%에 도달하니깐 status가 CLOSED ➡️ OPEN으로 바뀌었습니다.
그러면 CLOSED ➡️ OPEN으로 바뀐 순간 서킷 브레이커가 동작하고, 기존 함수를 타지 않고 서킷 브레이커에서 지정한 Fallbakc Method만 타게 됩니다.
그리고 정상으로 돌아왔는지 체크하기 위해서 서킷 브레이커는 HALF_OPEN 상태로 전환합니다. 전환 후 체크를 하고 제한된 수의 요청을 허용해서, 시스템이 정상 상태로 복구되었는지 확인합니다.
"Half-Open"상태에서 정상적인 요청 id : '11122"로 요청을 하면, 요청이 성공했기 때문에 서킷 브레이커는 클로즈드(Closed) 상태로 전환됩니다.
'Framework > Spring\Spring boot' 카테고리의 다른 글
[SpringCloud] JWT 토큰을 통한 Spring Cloud Gateway 인증 및 권한 관리 (0) | 2024.08.05 |
---|---|
[Spring Cloud] Spring Cloud Gateway로 MSA 환경에서의 효율적인 요청 처리와 로드 밸런싱 구현하기 (0) | 2024.08.03 |
[Spring Cloud] FeignClient와 Ribbon을 통한 클라이언트 사이드 로드 밸런싱 알아보기❗️ (0) | 2024.08.01 |
[Spring Cloud] Eureka 서버 및 Eureka클라이언트 설정과 서비스 디스커버리(FeignClient) (1) | 2024.08.01 |
[Spring MVC] Filter: 웹 애플리케이션 보안을 위한 필터의 역할과 활용법 (0) | 2024.08.01 |