각자가 책임지고 할 수있는 주된 역할을 미리 정해두는 게 좋을 것 같다는 의견에 따라, 역할 분담을 했다.
지난 프로젝트에서는 내 기능 구현에 집중해 지식을 쌓았지만 좀 더 프로젝트의 전체적으로 보며 지식을 쌓아가고 싶어서 발표를 맡았다!
다들 II[아이아이]라는 팀 이름에 맞게 (팀원 모두의 MBTI가 i, 11조라서) 쑥스러움을 많이 타시는지 선뜻 발표 역할이 정해지지 않아서 자신은 없지만 열심히 해보겠다며 낼름 손을 들어 보였다.
첫 날부터 발표가 정해지는 프로젝트는 처음이다.

사실 내가 지난 프로젝트에서 리뷰 도메인을 맡았던 이유는 개발에 자신이 없어서 내가 맡은 부분이 선행되어야 다른 분들이 개발을 할 수있는 부분을 최대한 줄여서 팀에게 폐를 끼치지 않는 것이 이유였다.
예를 들어 만약 내가 인증/인가와 같은 도메인을 맡아
먼저 구현이 되어야 다른 도메인에서 인증 처리를 구현할 수 있는데 내가 어려움을 겪고 시간을 끌게 된다면 프로젝트 전체의 일정이 늦춰질 수도 있기 때문이었다.
특히 전체 개발 흐름과 REST CRUD API 만드는 것만 확실하게 이해하고 넘어가자 라는 것이 나의 지난 프로젝트에서의 목표였다.
새로운 팀원들에게 이전 프로젝트에서 맡은 리뷰 도메인을 어떻게 구현했는지 설명했다.
팀원 분이 엔티티간 관계도 잘 몰랐었는데 스프링 배치까지 도입하려 한 점에 대해서 빨리 배우는 것 같다며 칭찬해주셨다.
팀원들에게 지난 프로젝트 경험을 공유하고 나니, 앞으로 프로젝트를 진행할 때 어떻게 해야할지 생각을 정리하게 되었다.
그래서 이번 프로젝트에서는 MSA구성으로 구현하는 만큼, 좀 더 전체적인 흐름을 볼 수 있는 도메인이나 기능을 맡으면 좋겠다.
서킷 브레이커는 마이크로서비스 간의 호출 실패를 감지하고 시스템의 전체적인 안정성을 유지하는 패턴으로,
외부 서비스 호출 실패 시 빠른 실패를 통해 장애를 격리하고, 시스템의 다른 부분에 영향을 주지 않도록 한다.
클로즈드 -> 오픈 -> 하프-오픈
Resilience4j는 서킷 브레이커 라이브러리로, 서비스 간의 호출 실패를 감지하고 시스템의 안정성을 유지한다.
다양한 서킷 브레이커 기능을 제공하며, 장애 격리 및 빠른 실패를 통해 복원력을 높인다.
오픈, 하프-오픈 상태를 통해 호출 실패를 관리하는 것
기본 상태로, 모든 요청을 통과시킨다.
이 상태에서 호출이 실패하면 실패 카운터가 증가
실패율이 설정된 임계값(예: 50%)을 초과하면 서킷 브레이커가 오픈 상태로 전환된다.
예시: 최근 5번의 호출 중 3번이 실패하여 실패율이 60%에 도달하면 오픈 상태로 전환됩니다.
서킷 브레이커가 오픈 상태로 전환되면 모든 요청을 실제로 실행하지 않고 바로 에러 응답을 반환해 실패로 처리한다.
설정된 대기 시간이 지난 후, 서킷 브레이커는 하프-오픈 상태로 전환된다.
예시: 서킷 브레이커가 오픈 상태로 전환되고 20초 동안 모든 요청이 차단됩니다.
하프-오픈 상태에서는 제한된 수의 요청을 허용하여 시스템이 정상 상태로 복구되었는지 확인한다.
요청이 성공하면 서킷 브레이커는 클로즈드 상태로 전환되지만
요청이 다시 실패하면 서킷 브레이커는 다시 오픈 상태로 전환된다.
예시:
하프-오픈 상태에서 3개의 요청을 허용하고,
모두 성공하면 클로즈드 상태로 전환됩니다.
만약 하나라도 실패하면 다시 오픈 상태로 전환됩니다.
호출 실패 시 대체 로직을 제공하여 시스템 안정성 확보하는 것
서킷 브레이커 상태를 모니터링하고 관리할 수 있는 다양한 도구 제공
Fallback 메서드는 외부 서비스 호출이 실패했을 때 대체 로직을 제공하는 메서드다.
시스템의 안정성을 높이고, 장애가 다른 서비스에 전파되는 것을 방지하여 장애가 발생해도 사용자에게 일정한 응답을 제공할 수 있다.
Prometheus와 Grafana를 사용하여 Resilience4j 서킷 브레이커의 상태를 실시간으로 모니터링할 수 있습니다.
Prometheus를 통해 수집된 메트릭을 Grafana 대시보드에서 시각화할 수 있다.
dependencies {
implementation 'io.github.resilience4j:resilience4j-micrometer'
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
http://${hostname}:${port}/actuator/prometheus 에 접속하여 서킷브레이커 항목을 확인 가능하다.
Resilience4j는 Spring Cloud Netflix 패키지의 일부로,
Eureka와 Ribbon 등 다른 Spring Cloud 구성 요소와 쉽게 통합할 수 있기 때문에
서비스 디스커버리와 로드 밸런싱을 함께 활용해 더욱 안정적인 마이크로서비스 아키텍처를 구축할 수 있다.
spring:
application:
name: my-service
cloud:
circuitbreaker:
resilience4j:
enabled: true

Resilience4j 공식 사이트에서 SpringBoot 2~3.x 버전을 위한 의존성 추가 안내가 나와있다.
dependencies {
implementation "io.github.resilience4j:resilience4j-spring-boot3:2.2.0
implementation "org.springframework.boot:spring-boot-starter-aop"
}

강의에서 resilience4j 의존성을 spring starter에서 추가해 사용하지 않고 Github에서 특정 버전을 가져와 사용하는데
Spring 계층으로 추상화되어 복잡한 설정이 추가돼 실습이 어려워지는 것을 방지하기 위함이라고 한다.
Spring으로 추상화된 resilience4j를 사용 할 땐 이니셜라이저에서 선택해 추가해주면 된다.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
private String id;
private String title;
}
+ lombok.Data 가 자동으로 생성해주는 것:
@Getter
@Setter
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@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 ("error".equals(productId)) {
log.warn("###Received empty body for productId: {}", productId);
throw new RuntimeException("productId의 타입이 올바르지 않습니다.");
}
return new Product(productId, "id: " + productId + " 상품의 상세 정보");
}
public Product fallbackGetProductDetails(String productId, Throwable t) {
log.error("####Fallback triggered for productId: {} due to: {}", productId, t.getMessage());
return new Product(
productId,
"요청하신 " + productId + " 상품대신 이걸 드려요"
);
}
// 이벤트 설명 표
// +---------------------------+-------------------------------------------------+--------------------------------------------+
// | 이벤트 | 설명 | 로그 출력 |
// +---------------------------+-------------------------------------------------+--------------------------------------------+
// | 상태 전환 (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: ... |
// +------------------------------------------+-------------------------------------------+-----------------------------------------------------------------+
}
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable String id) {
return productService.getProductDetails(id);
}
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초로 설정
| 설정 | 값 | 의미 |
|---|---|---|
| slidingWindowSize | 5 | 최근 5번 호출 기준으로 판단 |
| minimumNumberOfCalls | 5 | 5번 호출 전에는 상태 판단 안함 |
| failureRateThreshold | 50% | 실패율 50% 초과 시 OPEN |
| permittedNumberOfCallsInHalfOpenState | 3 | HALF_OPEN에서 3번 테스트 호출 허용 |
| waitDurationInOpenState | 20s | OPEN 후 20초 뒤 HALF_OPEN 전환 |
| 호출 | 결과 |
|---|---|
| 1 | 실패 |
| 2 | 실패 |
| 3 | 실패 |
| 4 | 성공 |
| 5 | 성공 |
➡ OPEN 상태 전환
➡ HALF_OPEN 상태 전환
| 호출 | 결과 |
|---|---|
| 1 | 성공 |
| 2 | 성공 |
| 3 | 성공 |
➡ CLOSED 복구
| 호출 | 결과 |
|---|---|
| 1 | 성공 |
| 2 | 실패 |
➡ 즉시 OPEN



http://localhost:19090/actuator/prometheus 에 들어가면 Spring Boot가 현재 애플리케이션의 메트릭 정보를 텍스트로 노출하여 인스턴스의 상태값을 확인 할 수 있다.
여기에는 서킷브레이커 상태, 성공 호출 수, 실패 호출 수, slow call 같은 모니터링 데이터가 들어있다.
Prometheus는 일정 주기로 이 주소를 호출하여 정보를 가져간다.
요청 발생
│
▼
Resilience4j
(서킷브레이커 동작)
│
│ metrics 생성
▼
Micrometer
(메트릭 표준화)
│
▼
/actuator/prometheus
│
▼
Prometheus
(메트릭 수집 & 저장)
│
▼
Grafana
(그래프 시각화)