Spring/MSA

서킷 브레이커 (Resilience4j)

2025. 2. 12. 22:25
목차
  1. 1. 서킷 브레이커(Circuit Breaker)란?
  2. 2. 왜 서킷 브레이커가 필요할까?
  3. 3. 서킷 브레이커의 동작 방식
  4. 4. 서킷 브레이커를 구현하는 방법
  5.  
  6. 5. 서킷 브레이커와 함께 사용하면 좋은 패턴
  7.  
  8. 6. 정리
  9. 1) 정상적인 요청 처리 (Closed 상태)
  10. 2) 반복적인 장애 발생 (Closed 상태 유지)
  11. 3) 에러율 증가로 인해 Open 상태로 전환
  12. 4) 서킷 브레이커가 열린 상태에서 요청 차단
  13. 5) 일정 시간이 지나고 Half-Open 상태로 전환 (재시도)

1. 서킷 브레이커(Circuit Breaker)란?

서킷 브레이커(Circuit Breaker)는 마치 전기 회로의 차단기처럼 시스템에서 장애가 발생했을 때 전체 시스템이 마비되는 것을 방지하는 보호 장치이다.
특히 마이크로서비스 아키텍처(MSA)나 분산 시스템에서 자주 사용되는데 특정 서비스가 과부하 상태가 되거나 응답이 느려질 경우 해당 서비스로 가는 요청을 일정 기간 차단하여 시스템을 보호하는 역할을 한다.

2. 왜 서킷 브레이커가 필요할까?

  1. 연쇄 장애(캐스케이딩 실패) 방지
    • 예를 들어 A 서비스가 B 서비스에 요청을 보내는데, B 서비스가 느려지거나 응답하지 않으면 A 서비스도 계속해서 대기하게 된다.
    • 이 상태가 지속되면 A 서비스뿐만 아니라 A 서비스에 의존하는 다른 서비스들도 영향을 받게 되면서 시스템 전체가 마비될 수도 있다.
    • 서킷 브레이커는 이런 상황을 방지하기 위해 일정 임계치를 넘으면 해당 요청을 차단한다.
  2. 리소스 낭비 방지
    • 실패가 예상되는 요청을 계속해서 보내는 건 CPU, 메모리, 네트워크 등 리소스를 낭비하는 일이다.
    • 서킷 브레이커를 사용하면 불필요한 요청을 차단하여 리소스를 절약할 수 있다.
  3. 빠른 장애 감지 및 복구
    • 서킷 브레이커는 주기적으로 서비스 상태를 점검하고 문제가 해결되면 자동으로 원래 상태로 돌아가도록 도와준다.
    • 이를 통해 시스템이 더 빠르게 장애에서 복구할 수 있다.

3. 서킷 브레이커의 동작 방식

서킷 브레이커는 보통 세 가지 상태로 작동한다.

  1. Closed(닫힘) - 정상 상태
    • 서비스가 정상적으로 응답하면 요청을 그대로 보냄.
    • 하지만 일정 횟수 이상 실패하면 Open 상태로 전환됨.
  2. Open(열림) - 차단 상태
    • 서비스 장애가 감지되면 요청을 차단하고 즉시 실패 응답을 반환함.
    • 일정 시간 동안 차단한 후, 다시 Half-Open 상태로 전환하여 서비스가 정상인지 확인함.
  3. Half-Open(반열림) - 시험 상태
    • 일정 시간이 지나면 일부 요청을 다시 보내서 서비스가 정상인지 확인함.
    • 성공하면 Closed 상태로 돌아가고, 실패하면 다시 Open 상태로 유지됨.

➡️ 이런 방식으로 시스템이 과부하를 견딜 수 있도록 보호하는 게 서킷 브레이커의 핵심 원리!

4. 서킷 브레이커를 구현하는 방법

서킷 브레이커를 구현하는 가장 대표적인 라이브러리는 다음과 같은데:

  1. Resilience4j (Spring Boot에서 많이 사용됨)
    • Spring Cloud 프로젝트에서 자주 활용되는 서킷 브레이커 라이브러리.
    • @CircuitBreaker 애너테이션을 사용하면 쉽게 적용할 수 있어.
     
  2. Hystrix (Netflix가 만든 라이브러리, 현재는 Resilience4j로 대체되는 추세)
    • 과거에 많이 사용되었지만 Netflix에서 유지보수를 중단하면서 점점 대체되고 있다.
  3. Istio (Kubernetes 환경에서 서킷 브레이커를 제공하는 서비스 메쉬)
    • Istio 같은 서비스 메쉬를 사용하면 애플리케이션 코드 수정 없이 서킷 브레이커를 적용할 수도 있다.
@RestController public class ExampleController { 
	
    @GetMapping("/unstable-service") 
    @CircuitBreaker(name = "exampleService", fallbackMethod = "fallbackResponse") 
    public String unstableService() { // 실제 호출할 서비스 (예: 외부 API) 
    	throw new RuntimeException("Service failure!"); 
    } 
    
    public String fallbackResponse(Exception e) { 
    	return "서비스가 현재 사용할 수 없습니다. 나중에 다시 시도해주세요."; 
    } 
 }

 

5. 서킷 브레이커와 함께 사용하면 좋은 패턴

  • 백오프(Backoff) 전략: 서킷 브레이커가 열린 후, 점진적으로 요청을 재시도하는 방식.
  • 폴백(Fallback) 처리: 서킷 브레이커가 요청을 차단하면 대체 응답(예: 캐시 데이터)을 반환하도록 설정.
  • 타임아웃 설정: 요청이 일정 시간 안에 응답하지 않으면 실패로 간주하여 서킷 브레이커가 개입하도록 설정.

 

6. 정리

  • 서킷 브레이커는 장애가 발생했을 때 전체 시스템을 보호하는 역할을 한다.
  •  3가지 상태(Closed, Open, Half-Open)를 가지며, 장애 감지 후 자동으로 복구 시도를 한다.
  •  Resilience4j 같은 라이브러리를 사용하면 쉽게 구현할 수 있다.
  •  백오프, 폴백 처리와 함께 사용하면 더 안정적인 시스템을 만들 수 있다.

즉, 서킷 브레이커는 장애를 감지하고 확산을 막아주는 자동 보호 장치이다.

 

 

 

 

- 정상 적인 요청 처리(Closed 상태)

 

2025-02-10T01:11:18.154+09:00  INFO 21612 --- [io-19090-exec-1] ProductService   : ###Fetching product details for productId: 1

 

 

 

[비정상 호출(111)]

Fallback 호출된다.

 

일정 시간 지나면 다시 호출 정상적으로 돌아온다.

[정상 호출]

2025-02-10T01:11:18.154+09:00  INFO 21612 --- [sample] [io-19090-exec-1] c.s.resilience4j.sample.ProductService   : ###Fetching product details for productId: 1
2025-02-10T01:11:34.854+09:00  INFO 21612 --- [sample] [io-19090-exec-2] c.s.resilience4j.sample.ProductService   : ###Fetching product details for productId: 1111
2025-02-10T01:11:37.055+09:00  INFO 21612 --- [sample] [io-19090-exec-3] c.s.resilience4j.sample.ProductService   : ###Fetching product details for productId: 1111
2025-02-10T01:11:37.829+09:00  INFO 21612 --- [sample] [io-19090-exec-4] c.s.resilience4j.sample.ProductService   : ###Fetching product details for productId: 1111
2025-02-10T01:11:38.683+09:00  INFO 21612 --- [sample] [io-19090-exec-5] c.s.resilience4j.sample.ProductService   : ###Fetching product details for productId: 1111
2025-02-10T01:11:39.464+09:00  INFO 21612 --- [sample] [io-19090-exec-6] c.s.resilience4j.sample.ProductService   : ###Fetching product details for productId: 1111
2025-02-10T01:11:54.797+09:00  INFO 21612 --- [sample] [io-19090-exec-7] c.s.resilience4j.sample.ProductService   : ###Fetching product details for productId: 111
2025-02-10T01:11:54.797+09:00  WARN 21612 --- [sample] [io-19090-exec-7] c.s.resilience4j.sample.ProductService   : ###Received empty body for productId: 111
2025-02-10T01:11:54.798+09:00  INFO 21612 --- [sample] [io-19090-exec-7] c.s.resilience4j.sample.ProductService   : #######CircuitBreaker Error: 2025-02-10T01:11:54.797394200+09:00[Asia/Seoul]: CircuitBreaker 'productService' recorded an error: 'java.lang.RuntimeException: Empty response body'. Elapsed time: 0 ms
2025-02-10T01:11:54.799+09:00 ERROR 21612 --- [sample] [io-19090-exec-7] c.s.resilience4j.sample.ProductService   : ####Fallback triggered for productId: 111 due to: Empty response body
2025-02-10T01:11:56.393+09:00  INFO 21612 --- [sample] [io-19090-exec-8] c.s.resilience4j.sample.ProductService   : ###Fetching product details for productId: 111
2025-02-10T01:11:56.393+09:00  WARN 21612 --- [sample] [io-19090-exec-8] c.s.resilience4j.sample.ProductService   : ###Received empty body for productId: 111
2025-02-10T01:11:56.394+09:00  INFO 21612 --- [sample] [io-19090-exec-8] c.s.resilience4j.sample.ProductService   : #######CircuitBreaker Error: 2025-02-10T01:11:56.393930500+09:00[Asia/Seoul]: CircuitBreaker 'productService' recorded an error: 'java.lang.RuntimeException: Empty response body'. Elapsed time: 0 ms
2025-02-10T01:11:56.394+09:00 ERROR 21612 --- [sample] [io-19090-exec-8] c.s.resilience4j.sample.ProductService   : ####Fallback triggered for productId: 111 due to: Empty response body
2025-02-10T01:11:57.044+09:00  INFO 21612 --- [sample] [io-19090-exec-9] c.s.resilience4j.sample.ProductService   : ###Fetching product details for productId: 111
2025-02-10T01:11:57.044+09:00  WARN 21612 --- [sample] [io-19090-exec-9] c.s.resilience4j.sample.ProductService   : ###Received empty body for productId: 111
2025-02-10T01:11:57.045+09:00  INFO 21612 --- [sample] [io-19090-exec-9] c.s.resilience4j.sample.ProductService   : #######CircuitBreaker Error: 2025-02-10T01:11:57.045718600+09:00[Asia/Seoul]: CircuitBreaker 'productService' recorded an error: 'java.lang.RuntimeException: Empty response body'. Elapsed time: 0 ms
2025-02-10T01:11:57.046+09:00  INFO 21612 --- [sample] [io-19090-exec-9] c.s.resilience4j.sample.ProductService   : #######CircuitBreaker Failure Rate Exceeded: 2025-02-10T01:11:57.046195500+09:00[Asia/Seoul]: CircuitBreaker 'productService' exceeded failure rate threshold. Current failure rate: 60.0
2025-02-10T01:11:57.049+09:00  INFO 21612 --- [sample] [io-19090-exec-9] c.s.resilience4j.sample.ProductService   : #######CircuitBreaker State Transition: 2025-02-10T01:11:57.049697800+09:00[Asia/Seoul]: CircuitBreaker 'productService' changed state from CLOSED to OPEN
2025-02-10T01:11:57.050+09:00 ERROR 21612 --- [sample] [io-19090-exec-9] c.s.resilience4j.sample.ProductService   : ####Fallback triggered for productId: 111 due to: Empty response body
2025-02-10T01:12:00.042+09:00  INFO 21612 --- [sample] [o-19090-exec-10] c.s.resilience4j.sample.ProductService   : #######CircuitBreaker Call Not Permitted: 2025-02-10T01:12:00.042697200+09:00[Asia/Seoul]: CircuitBreaker 'productService' recorded a call which was not permitted.
2025-02-10T01:12:00.042+09:00 ERROR 21612 --- [sample] [o-19090-exec-10] c.s.resilience4j.sample.ProductService   : ####Fallback triggered for productId: 1 due to: CircuitBreaker 'productService' is OPEN and does not permit further calls
2025-02-10T01:12:00.784+09:00  INFO 21612 --- [sample] [io-19090-exec-1] c.s.resilience4j.sample.ProductService   : #######CircuitBreaker Call Not Permitted: 2025-02-10T01:12:00.784362100+09:00[Asia/Seoul]: CircuitBreaker 'productService' recorded a call which was not permitted.
2025-02-10T01:12:00.784+09:00 ERROR 21612 --- [sample] [io-19090-exec-1] c.s.resilience4j.sample.ProductService   : ####Fallback triggered for productId: 1. due to: CircuitBreaker 'productService' is OPEN and does not permit further calls
2025-02-10T01:12:04.999+09:00  INFO 21612 --- [sample] [io-19090-exec-2] c.s.resilience4j.sample.ProductService   : #######CircuitBreaker Call Not Permitted: 2025-02-10T01:12:04.999094500+09:00[Asia/Seoul]: CircuitBreaker 'productService' recorded a call which was not permitted.
2025-02-10T01:12:04.999+09:00 ERROR 21612 --- [sample] [io-19090-exec-2] c.s.resilience4j.sample.ProductService   : ####Fallback triggered for productId: 1 due to: CircuitBreaker 'productService' is OPEN and does not permit further calls
2025-02-10T01:12:06.824+09:00  INFO 21612 --- [sample] [io-19090-exec-3] c.s.resilience4j.sample.ProductService   : #######CircuitBreaker Call Not Permitted: 2025-02-10T01:12:06.823139200+09:00[Asia/Seoul]: CircuitBreaker 'productService' recorded a call which was not permitted.
2025-02-10T01:12:06.824+09:00 ERROR 21612 --- [sample] [io-19090-exec-3] c.s.resilience4j.sample.ProductService   : ####Fallback triggered for productId: 1 due to: CircuitBreaker 'productService' is OPEN and does not permit further calls
2025-02-10T01:12:09.696+09:00  INFO 21612 --- [sample] [io-19090-exec-4] c.s.resilience4j.sample.ProductService   : #######CircuitBreaker Call Not Permitted: 2025-02-10T01:12:09.696482300+09:00[Asia/Seoul]: CircuitBreaker 'productService' recorded a call which was not permitted.
2025-02-10T01:12:09.696+09:00 ERROR 21612 --- [sample] [io-19090-exec-4] c.s.resilience4j.sample.ProductService   : ####Fallback triggered for productId: 1 due to: CircuitBreaker 'productService' is OPEN and does not permit further calls
2025-02-10T01:12:13.257+09:00  INFO 21612 --- [sample] [io-19090-exec-5] c.s.resilience4j.sample.ProductService   : #######CircuitBreaker Call Not Permitted: 2025-02-10T01:12:13.257059600+09:00[Asia/Seoul]: CircuitBreaker 'productService' recorded a call which was not permitted.
2025-02-10T01:12:13.257+09:00 ERROR 21612 --- [sample] [io-19090-exec-5] c.s.resilience4j.sample.ProductService   : ####Fallback triggered for productId: 1 due to: CircuitBreaker 'productService' is OPEN and does not permit further calls
2025-02-10T01:12:17.393+09:00  INFO 21612 --- [sample] [io-19090-exec-6] c.s.resilience4j.sample.ProductService   : #######CircuitBreaker State Transition: 2025-02-10T01:12:17.393128400+09:00[Asia/Seoul]: CircuitBreaker 'productService' changed state from OPEN to HALF_OPEN
2025-02-10T01:12:17.394+09:00  INFO 21612 --- [sample] [io-19090-exec-6] c.s.resilience4j.sample.ProductService   : ###Fetching product details for productId: 1

맨 처음 정상 호출(1)하다가 오류 상황 발생(111)

오류 상황 체크하다가 Current failure rate가 증가하여 Current failure에 도달하면 상태가 CLOSED 에서 OPEN으로 변경 

OPEN으로 바뀐 순간부터는 원래 함수를 타지 않고 Fallback 메소드를 통해 바로 return 

일정 시간 지나고 OPEN에서 HALF_OPEN으로 변경하여 기존 함수 호출

몇 번의 호출 후 문제가 없으면 HALPF_OPEN에서 CLOSED로 상태 변환  

 

 

1) 정상적인 요청 처리 (Closed 상태)

2025-02-10T01:11:18.154+09:00  INFO 21612 --- [io-19090-exec-1] ProductService   : ###Fetching product details for productId: 1

 

-  productId: 1에 대한 요청이 정상적으로 처리되고 있다.
- 서킷 브레이커는 아직 Closed(닫힘) 상태이며, 모든 요청이 서비스로 전달되고 있습니다.


2) 반복적인 장애 발생 (Closed 상태 유지)

2025-02-10T01:11:54.797+09:00  WARN 21612 --- [io-19090-exec-7] ProductService   : ###Received empty body for productId: 111
2025-02-10T01:11:54.798+09:00  INFO 21612 --- [io-19090-exec-7] ProductService   : #######CircuitBreaker Error: CircuitBreaker 'productService' recorded an error: 'java.lang.RuntimeException: Empty response body'
2025-02-10T01:11:54.799+09:00 ERROR 21612 --- [io-19090-exec-7] ProductService   : ####Fallback triggered for productId: 111 due to: Empty response body

 

❌ productId: 111 요청에서 빈 응답(Empty response body)이 반환됨.
❌ RuntimeException 발생 → 서킷 브레이커가 에러를 기록하고, 폴백(fallback) 로직이 실행됨.
- 하지만, 아직 서킷 브레이커는 Closed(닫힘) 상태이며, 계속 요청을 시도함.


3) 에러율 증가로 인해 Open 상태로 전환

2025-02-10T01:11:57.045+09:00  INFO 21612 --- [io-19090-exec-9] ProductService   : #######CircuitBreaker Failure Rate Exceeded: CircuitBreaker 'productService' exceeded failure rate threshold. Current failure rate: 60.0
2025-02-10T01:11:57.049+09:00  INFO 21612 --- [io-19090-exec-9] ProductService   : #######CircuitBreaker State Transition: CircuitBreaker 'productService' changed state from CLOSED to OPEN

🚨 일정 비율 이상의 실패(60%)가 발생하면서, 서킷 브레이커가 Closed → Open 상태로 변경됨.
🚨 Open 상태에서는 새로운 요청을 차단하여 서비스가 추가적인 부하를 받지 않도록 보호함.


4) 서킷 브레이커가 열린 상태에서 요청 차단

2025-02-10T01:12:00.042+09:00  INFO 21612 --- [io-19090-exec-10] ProductService   : #######CircuitBreaker Call Not Permitted: CircuitBreaker 'productService' recorded a call which was not permitted.
2025-02-10T01:12:00.042+09:00 ERROR 21612 --- [io-19090-exec-10] ProductService   : ####Fallback triggered for productId: 1 due to: CircuitBreaker 'productService' is OPEN and does not permit further calls

❌ 서킷 브레이커가 Open 상태이므로, 새로운 요청이 차단됨.
- productId: 1 요청도 실패하며, 즉시 Fallback 응답을 반환.


5) 일정 시간이 지나고 Half-Open 상태로 전환 (재시도)

2025-02-10T01:12:17.393+09:00  INFO 21612 --- [io-19090-exec-6] ProductService   : #######CircuitBreaker State Transition: CircuitBreaker 'productService' changed state from OPEN to HALF_OPEN
2025-02-10T01:12:17.394+09:00  INFO 21612 --- [io-19090-exec-6] ProductService   : ###Fetching product details for productId: 1

🔄 서킷 브레이커가 Open → Half-Open 상태로 변경됨.
🔄 Half-Open 상태에서는 일부 요청만 허용하여 서비스가 정상적으로 복구되었는지 확인함.
- 만약 성공적인 응답이 일정 수준 이상 발생하면 Closed 상태로 복귀함.
- 반대로, 실패하면 다시 Open 상태로 전환됨.

'Spring > MSA' 카테고리의 다른 글

MSA 환경에서 FeignClient 응답 데이터가 누락된 이유는? – CommonResponse<DTO>와 변수 범위 이슈  (0) 2025.03.24
MSA 주문 서비스에서 보상 트랜잭션(SAGA 오케스트레이션) 적용기  (0) 2025.03.21
MSA에서 JPA 트랜잭션 문제 & SAGA 패턴 필요성  (0) 2025.03.17
[MSA] MSA 기반 상품 주문 시스템 (1)  (0) 2025.03.07
Spring cloud와 MSA  (0) 2025.02.11
  1. 1. 서킷 브레이커(Circuit Breaker)란?
  2. 2. 왜 서킷 브레이커가 필요할까?
  3. 3. 서킷 브레이커의 동작 방식
  4. 4. 서킷 브레이커를 구현하는 방법
  5.  
  6. 5. 서킷 브레이커와 함께 사용하면 좋은 패턴
  7.  
  8. 6. 정리
  9. 1) 정상적인 요청 처리 (Closed 상태)
  10. 2) 반복적인 장애 발생 (Closed 상태 유지)
  11. 3) 에러율 증가로 인해 Open 상태로 전환
  12. 4) 서킷 브레이커가 열린 상태에서 요청 차단
  13. 5) 일정 시간이 지나고 Half-Open 상태로 전환 (재시도)
'Spring/MSA' 카테고리의 다른 글
  • MSA 주문 서비스에서 보상 트랜잭션(SAGA 오케스트레이션) 적용기
  • MSA에서 JPA 트랜잭션 문제 & SAGA 패턴 필요성
  • [MSA] MSA 기반 상품 주문 시스템 (1)
  • Spring cloud와 MSA
챛채
챛채
챛채
챛 Development Log
챛채
전체
오늘
어제
  • IT (104)
    • Front (5)
      • Thymeleaf (1)
    • Language (4)
      • JAVA (4)
    • Spring (37)
      • JPA (4)
      • Spring boot (13)
      • Security (1)
      • MSA (6)
      • Kafka (2)
    • DBMS (6)
      • Redis (6)
    • CS (1)
    • Algorithm (45)
      • 이론 (3)
      • 백준 (1)
      • 문제 (41)
    • 자격증 (1)
      • 정보처리기사 (필기) (0)
      • 정보처리기사 (실기) (1)
    • 프로젝트 (3)
      • chaelog (3)

블로그 메뉴

    최근 글

    hELLO · Designed By 정상우.
    챛채
    서킷 브레이커 (Resilience4j)
    상단으로

    티스토리툴바

    개인정보

    • 티스토리 홈
    • 포럼
    • 로그인

    단축키

    내 블로그

    내 블로그 - 관리자 홈 전환
    Q
    Q
    새 글 쓰기
    W
    W

    블로그 게시글

    글 수정 (권한 있는 경우)
    E
    E
    댓글 영역으로 이동
    C
    C

    모든 영역

    이 페이지의 URL 복사
    S
    S
    맨 위로 이동
    T
    T
    티스토리 홈 이동
    H
    H
    단축키 안내
    Shift + /
    ⇧ + /

    * 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.