
실패 카운터가 증가합니다.resilience4j 의존성은 spring starter에서 추가하여 사용하지 않을 것입니다.
”io.github.resilience4j:resilience4j-spring-boot3:2.2.0” 를 사용합니다.
”boot3” 임을 주의 하세요. > gpt에서 오류 찾기 힘들 수 있다.
build.gradle 파일 예시:
dependencies {
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-aop'
}
application.yml 파일에서 설정할 수 있습니다.
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초로 설정
설정은 프로젝트에 따라 미리 되어있을 수도 있고, 필요한 설정만 추가하는 방식으로 수정할 수도 있다. default로 사용한 뒤에 그때그때 추가하는 것이 좋다.
Fallback 메서드는 외부 서비스 호출이 실패했을 때 대체 로직을 제공하는 메서드입니다.
예시 코드:
@Service
public class MyService {
//fallbackMethod : 실패 시 실행할 대체 메소드
@CircuitBreaker(name = "myService", fallbackMethod = "fallbackMethod")
public String myMethod() {
// 외부 서비스 호출
return externalService.call();
}
public String fallbackMethod(Throwable t) {
return "Fallback response";
}
}
Fallback 메서드에 작성하면 좋을 내용
fallback 발생 시 alert (sns 등을 통해 개발자에게 알림)
사용자에게 대체 로직이나 현재 반응이 불가하다는 것을 알림
에러가 발생해도 에러처럼 보이지 않게 하자!
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 #가 수집한 데이터를 Grafana에서 상태 모니터링
prometheus:
metrics:
export:
enabled: true
spring:
application:
name: my-service
cloud:
circuitbreaker:
resilience4j:
enabled: true
15'40 부터
💡 이번 실습에서 유레카는 사용하지 않겠습니다.
프로젝트에서는 상품을 조회하는것을 가정하겠습니다.
상품 아이디 111을 호출하면 에러를 발생시켜 fallbackMethod 를 실행하는것을 확인합니다.
또한 이벤트리스너를 사용하여 서킷브레이커의 상태를 조회해보겠습니다.
❗ 주의 starter에서
resilience4j디펜던시를 추가하지 않습니다.
starter 에서 추가하면 “org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j”가 추가됩니다.
이는 추상화 계층을 통해 Resilience4j를 사용하는데 (구현체가 없고 인터페이스만 있어서 우리가 생성해야 함) 우리는 직접 resilience4j를 사용하기 위해 build.gradle에 “io.github.resilience4j:resilience4j-spring-boot3:2.2.0”를 추가하겠습니다.
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()
}
github 문서의 document를 바탕으로 버전에 맞는 의존성을 추가해야 한다.
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);
}
}private final CircuitBreakerRegistry circuitBreakerRegistry;를 통해 서킷 브레이커 사용
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;
// 서킷 브레이커 설정, 실패 시 수행할 메소드 지정
@CircuitBreaker(name = "productService", fallbackMethod = "fallbackGetProductDetails")
public Product getProductDetails(String productId) {
log.info("###Fetching product details for productId: {}", productId);
// 아이디가 111이면 오류 발생 가정 > 로그 작성, 에러 던짐
if ("111".equals(productId)) {
log.warn("###Received empty body for productId: {}", productId);
throw new RuntimeException("Empty response body");
}
return new Product(
productId,
"Sample Product"
); // 성공 시 값 리턴
}
// getProductDetails 실패 시 수행할 메소드
public Product fallbackGetProductDetails(String productId, Throwable t) {
log.error("####Fallback triggered for productId: {} due to: {}", productId, t.getMessage());
return new Product(
productId,
"Fallback Product"
); // 실패 시 값 리턴, 일정 시간 동안은 이 메소드가 수행됨
}
}
// 상태 변경 체크 용도 메소드
@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)); // 오류 발생 이벤트 리스너
}
이벤트 설명 표
+---------------------------+-------------------------------------------------+--------------------------------------------+
| 이벤트 | 설명 | 로그 출력 |
+---------------------------+-------------------------------------------------+--------------------------------------------+
| 상태 전환 (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: ... |
+------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+
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: truegetProductDetails 함수를 타지않고 바로 fallbackGetProductDetails 로 호출 되는것을 확인 할 수 있습니다.