Chuyển đến nội dung chính
DevOps

Circuit Breaker Pattern trong Spring Boot

Tìm hiểu Circuit Breaker Pattern trong Spring Boot với Resilience4j - từ nguyên lý hoạt động, cấu hình chi tiết, ví dụ thực tế đến best practices. Hướng dẫn đầy đủ giúp bạn xây dựng hệ thống microservices resilient, ngăn chặn cascading failures và tự động recovery khi service gặp sự cố.

Circuit Breaker Pattern trong Spring Boot

1. Giới thiệu

Circuit Breaker (Cầu dao ngắt mạch) là một design pattern quan trọng trong kiến trúc microservices, được lấy cảm hứng từ thiết bị cầu dao điện trong thực tế. Giống như cầu dao điện tự động ngắt mạch khi phát hiện quá tải để bảo vệ hệ thống điện, Circuit Breaker trong phần mềm sẽ tự động ngắt các request đến service đang gặp sự cố để bảo vệ toàn bộ hệ thống.

Pattern này giải quyết vấn đề gì?

Trong kiến trúc microservices, các service phụ thuộc lẫn nhau. Khi một service gặp sự cố (chậm hoặc không phản hồi), nếu không có cơ chế bảo vệ:

[Service A] ---> [Service B (đang chết)] ---> timeout 30s
     ↓
  Threads bị block
     ↓
  Resource exhaustion
     ↓
  Service A cũng chết (Cascading Failure)

Circuit Breaker ngăn chặn hiệu ứng domino này bằng cách fail fast - trả về lỗi ngay lập tức thay vì chờ đợi vô vọng.


2. Tại sao cần Circuit Breaker?

2.1. Vấn đề Cascading Failure

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Gateway   │────▶│  Order Svc  │────▶│ Payment Svc │ ← Đang chết
└─────────────┘     └─────────────┘     └─────────────┘
                           │
                           ▼
                    ┌─────────────┐
                    │Inventory Svc│
                    └─────────────┘

Khi Payment Service chết:

  1. Order Service chờ response → threads bị block
  2. Requests đến Order Service tăng lên → thread pool cạn kiệt
  3. Order Service không thể xử lý request mới → cũng "chết"
  4. Gateway timeout → User experience tệ

2.2. Lợi ích của Circuit Breaker

Lợi ích Mô tả
Fail Fast Trả về lỗi ngay, không chờ timeout
Bảo vệ Resources Giải phóng threads, connections
Tự động Recovery Tự kiểm tra và phục hồi khi service hoạt động lại
Graceful Degradation Cung cấp fallback response thay vì lỗi hoàn toàn
Monitoring Cung cấp metrics về health của dependencies

3. Nguyên lý hoạt động

3.1. Ba trạng thái của Circuit Breaker

                    ┌──────────────────────────────────────┐
                    │                                      │
                    ▼                                      │
┌─────────┐    failure     ┌─────────┐    wait timeout    ┌─────────────┐
│ CLOSED  │───────────────▶│  OPEN   │──────────────────▶│ HALF_OPEN   │
│(Đóng)   │  threshold     │ (Mở)    │                   │ (Nửa mở)    │
└─────────┘                └─────────┘                   └─────────────┘
     ▲                          ▲                              │
     │                          │                              │
     │    success               │         failure              │
     └──────────────────────────┴──────────────────────────────┘

CLOSED (Đóng) - Trạng thái bình thường

  • Tất cả requests được cho phép đi qua
  • Circuit Breaker theo dõi tỷ lệ lỗi
  • Khi failure rate vượt ngưỡng → chuyển sang OPEN

OPEN (Mở) - Trạng thái bảo vệ

  • Tất cả requests bị reject ngay lập tức
  • Trả về fallback response hoặc exception
  • Sau một khoảng thời gian (wait duration) → chuyển sang HALF_OPEN

HALF_OPEN (Nửa mở) - Trạng thái thử nghiệm

  • Cho phép một số lượng requests nhất định đi qua để test
  • Nếu thành công → về CLOSED
  • Nếu thất bại → về OPEN

3.2. Sliding Window

Circuit Breaker sử dụng sliding window để tính toán failure rate:

Count-based sliding window:

[Request 1: ✓] [Request 2: ✗] [Request 3: ✓] [Request 4: ✗] [Request 5: ✗]
                                                              ↑
                                              Failure rate = 3/5 = 60%

Time-based sliding window:

|-------- 10 seconds --------|
| ✓ ✗ ✓ ✗ ✗ ✓ ✗ ✗ ✓ ✓ |
| Failure rate = 5/10 = 50%  |

4. Cài đặt với Spring Boot và Resilience4j

4.1. Tại sao chọn Resilience4j?

Hystrix (Netflix) đã deprecated từ 2018. Resilience4j là thư viện được khuyến nghị:

  • Lightweight, không có transitive dependencies
  • Thiết kế cho Java 8+ với functional programming
  • Tích hợp tốt với Spring Boot
  • Hỗ trợ reactive (Project Reactor, RxJava)

4.2. Dependencies

<!-- pom.xml -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2023.0.3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>

&lt;!-- Resilience4j Circuit Breaker --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
    &lt;artifactId&gt;spring-cloud-starter-circuitbreaker-resilience4j&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;!-- AOP cho annotations --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-aop&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;!-- Actuator cho monitoring --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;
&lt;/dependency&gt;

</dependencies>

Hoặc với Gradle:

// build.gradle
ext {
springCloudVersion = "2023.0.3"
}

dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j' implementation 'org.springframework.boot:spring-boot-starter-aop' implementation 'org.springframework.boot:spring-boot-starter-actuator' }

dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" } }


5. Cấu hình chi tiết

5.1. Cấu hình qua application.yml

# application.yml
resilience4j:
circuitbreaker:
configs:
# Cấu hình mặc định cho tất cả circuit breakers
default:
# Số lượng calls trong sliding window để tính failure rate
slidingWindowSize: 10

    # Loại sliding window: COUNT_BASED hoặc TIME_BASED
    slidingWindowType: COUNT_BASED
    
    # Số calls tối thiểu trước khi tính failure rate
    minimumNumberOfCalls: 5
    
    # Tỷ lệ lỗi (%) để chuyển sang OPEN
    failureRateThreshold: 50
    
    # Tỷ lệ slow calls (%) để chuyển sang OPEN
    slowCallRateThreshold: 100
    
    # Thời gian được coi là slow call (ms)
    slowCallDurationThreshold: 2000
    
    # Thời gian ở trạng thái OPEN trước khi chuyển sang HALF_OPEN
    waitDurationInOpenState: 30s
    
    # Số calls được phép trong trạng thái HALF_OPEN
    permittedNumberOfCallsInHalfOpenState: 3
    
    # Tự động chuyển từ OPEN sang HALF_OPEN
    automaticTransitionFromOpenToHalfOpenEnabled: true
    
    # Các exception được ghi nhận là failure
    recordExceptions:
      - java.io.IOException
      - java.net.ConnectException
      - java.util.concurrent.TimeoutException
      - org.springframework.web.client.HttpServerErrorException
    
    # Các exception KHÔNG được ghi nhận là failure
    ignoreExceptions:
      - com.example.BusinessException

# Cấu hình cho từng circuit breaker cụ thể
instances:
  # Circuit breaker cho Payment Service
  paymentService:
    baseConfig: default
    failureRateThreshold: 30
    waitDurationInOpenState: 20s
    slidingWindowSize: 20
  
  # Circuit breaker cho Inventory Service
  inventoryService:
    baseConfig: default
    failureRateThreshold: 60
    slowCallDurationThreshold: 3000

Actuator endpoints

management: endpoints: web: exposure: include: health,circuitbreakers,circuitbreakerevents health: circuitbreakers: enabled: true endpoint: health: show-details: always

5.2. Giải thích các tham số quan trọng

Tham số Mô tả Giá trị khuyến nghị
slidingWindowSize Số lượng calls để tính failure rate 10-100 tùy traffic
slidingWindowType COUNT_BASED hoặc TIME_BASED COUNT_BASED cho traffic thấp
minimumNumberOfCalls Số calls tối thiểu trước khi đánh giá 5-10
failureRateThreshold % lỗi để mở circuit 50% là phổ biến
waitDurationInOpenState Thời gian chờ trước khi thử lại 30s-60s
permittedNumberOfCallsInHalfOpenState Số calls test trong HALF_OPEN 3-10

6. Ví dụ thực tế

6.1. Cấu trúc Project

order-service/
├── src/main/java/com/example/order/
│   ├── OrderServiceApplication.java
│   ├── config/
│   │   └── Resilience4jConfig.java
│   ├── controller/
│   │   └── OrderController.java
│   ├── service/
│   │   ├── OrderService.java
│   │   └── PaymentServiceClient.java
│   ├── dto/
│   │   ├── OrderRequest.java
│   │   ├── OrderResponse.java
│   │   └── PaymentResponse.java
│   └── exception/
│       └── ServiceUnavailableException.java
├── src/main/resources/
│   └── application.yml
└── pom.xml

6.2. Payment Service Client với Circuit Breaker

package com.example.order.service;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import io.github.resilience4j.retry.annotation.Retry; import io.github.resilience4j.timelimiter.annotation.TimeLimiter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate;

import java.util.concurrent.CompletableFuture;

@Service @RequiredArgsConstructor @Slf4j public class PaymentServiceClient {

private final RestTemplate restTemplate;
private static final String PAYMENT_SERVICE_URL = "http://payment-service:8080";

/**
 * Gọi Payment Service với Circuit Breaker bảo vệ
 * 
 * Thứ tự xử lý: Retry -&gt; CircuitBreaker -&gt; TimeLimiter -&gt; Bulkhead
 */
@CircuitBreaker(name = "paymentService", fallbackMethod = "processPaymentFallback")
@Retry(name = "paymentService")
@TimeLimiter(name = "paymentService")
public CompletableFuture&lt;PaymentResponse&gt; processPayment(PaymentRequest request) {
    log.info("Calling Payment Service for order: {}", request.getOrderId());
    
    return CompletableFuture.supplyAsync(() -&gt; {
        PaymentResponse response = restTemplate.postForObject(
            PAYMENT_SERVICE_URL + "/api/payments",
            request,
            PaymentResponse.class
        );
        
        log.info("Payment processed successfully: {}", response);
        return response;
    });
}

/**
 * Fallback method khi Circuit Breaker OPEN hoặc có lỗi
 * 
 * Phải có cùng parameters + thêm Exception/Throwable
 */
public CompletableFuture&lt;PaymentResponse&gt; processPaymentFallback(
        PaymentRequest request, 
        Throwable throwable) {
    
    log.warn("Payment Service unavailable. Triggering fallback for order: {}. Error: {}", 
            request.getOrderId(), 
            throwable.getMessage());
    
    // Option 1: Trả về response mặc định
    PaymentResponse fallbackResponse = PaymentResponse.builder()
            .orderId(request.getOrderId())
            .status("PENDING")
            .message("Payment will be processed when service is available")
            .fallback(true)
            .build();
    
    // Option 2: Có thể queue lại để xử lý sau
    // paymentQueue.add(request);
    
    return CompletableFuture.completedFuture(fallbackResponse);
}

/**
 * Ví dụ với synchronous call (không dùng TimeLimiter)
 */
@CircuitBreaker(name = "paymentService", fallbackMethod = "getPaymentStatusFallback")
@Retry(name = "paymentService")
public PaymentResponse getPaymentStatus(String paymentId) {
    log.info("Getting payment status for: {}", paymentId);
    
    return restTemplate.getForObject(
        PAYMENT_SERVICE_URL + "/api/payments/" + paymentId,
        PaymentResponse.class
    );
}

public PaymentResponse getPaymentStatusFallback(String paymentId, Throwable throwable) {
    log.warn("Cannot get payment status for: {}. Error: {}", paymentId, throwable.getMessage());
    
    return PaymentResponse.builder()
            .paymentId(paymentId)
            .status("UNKNOWN")
            .message("Service temporarily unavailable")
            .fallback(true)
            .build();
}

}

6.3. Order Service

package com.example.order.service;

import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service;

@Service @RequiredArgsConstructor @Slf4j public class OrderService {

private final PaymentServiceClient paymentClient;
private final InventoryServiceClient inventoryClient;
private final OrderRepository orderRepository;
private final CircuitBreakerRegistry circuitBreakerRegistry;

public OrderResponse createOrder(OrderRequest request) {
    log.info("Creating order: {}", request);
    
    // 1. Kiểm tra inventory
    InventoryResponse inventory = inventoryClient.checkInventory(request.getProductId());
    
    if (!inventory.isAvailable()) {
        throw new InsufficientInventoryException("Product not available");
    }
    
    // 2. Tạo order với status PENDING
    Order order = Order.builder()
            .customerId(request.getCustomerId())
            .productId(request.getProductId())
            .quantity(request.getQuantity())
            .status(OrderStatus.PENDING)
            .build();
    
    order = orderRepository.save(order);
    
    // 3. Xử lý payment (có Circuit Breaker bảo vệ)
    try {
        PaymentResponse payment = paymentClient.processPayment(
            PaymentRequest.builder()
                .orderId(order.getId())
                .amount(request.getAmount())
                .build()
        ).get(); // Blocking call
        
        if (payment.isFallback()) {
            // Payment đang trong queue, cần xử lý sau
            order.setStatus(OrderStatus.PAYMENT_PENDING);
        } else if ("SUCCESS".equals(payment.getStatus())) {
            order.setStatus(OrderStatus.CONFIRMED);
        } else {
            order.setStatus(OrderStatus.PAYMENT_FAILED);
        }
        
    } catch (Exception e) {
        log.error("Payment processing failed", e);
        order.setStatus(OrderStatus.PAYMENT_PENDING);
    }
    
    order = orderRepository.save(order);
    
    return OrderResponse.from(order);
}

/**
 * Kiểm tra trạng thái Circuit Breaker programmatically
 */
public CircuitBreakerStatus getCircuitBreakerStatus(String name) {
    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(name);
    CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();
    
    return CircuitBreakerStatus.builder()
            .name(name)
            .state(circuitBreaker.getState().name())
            .failureRate(metrics.getFailureRate())
            .slowCallRate(metrics.getSlowCallRate())
            .numberOfBufferedCalls(metrics.getNumberOfBufferedCalls())
            .numberOfFailedCalls(metrics.getNumberOfFailedCalls())
            .numberOfSuccessfulCalls(metrics.getNumberOfSuccessfulCalls())
            .numberOfSlowCalls(metrics.getNumberOfSlowCalls())
            .build();
}

}

6.4. Cấu hình Programmatic (thay thế cho YAML)

package com.example.order.config;

import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import io.github.resilience4j.common.circuitbreaker.configuration.CircuitBreakerConfigCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;

import java.io.IOException; import java.net.ConnectException; import java.time.Duration; import java.util.concurrent.TimeoutException;

@Configuration public class Resilience4jConfig {

@Bean
public CircuitBreakerConfigCustomizer paymentServiceCustomizer() {
    return CircuitBreakerConfigCustomizer.of("paymentService", builder -&gt; 
        builder
            .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
            .slidingWindowSize(10)
            .minimumNumberOfCalls(5)
            .failureRateThreshold(50)
            .slowCallRateThreshold(80)
            .slowCallDurationThreshold(Duration.ofSeconds(2))
            .waitDurationInOpenState(Duration.ofSeconds(30))
            .permittedNumberOfCallsInHalfOpenState(3)
            .automaticTransitionFromOpenToHalfOpenEnabled(true)
            .recordExceptions(
                IOException.class,
                ConnectException.class,
                TimeoutException.class
            )
    );
}

/**
 * Tạo Circuit Breaker programmatically với custom config
 */
@Bean
public CircuitBreaker customCircuitBreaker() {
    CircuitBreakerConfig config = CircuitBreakerConfig.custom()
            .slidingWindowSize(20)
            .failureRateThreshold(40)
            .waitDurationInOpenState(Duration.ofSeconds(60))
            .permittedNumberOfCallsInHalfOpenState(5)
            .build();
    
    CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config);
    
    CircuitBreaker circuitBreaker = registry.circuitBreaker("customService");
    
    // Đăng ký event listeners
    circuitBreaker.getEventPublisher()
            .onStateTransition(event -&gt; 
                System.out.println("State transition: " + event.getStateTransition()))
            .onFailureRateExceeded(event -&gt; 
                System.out.println("Failure rate exceeded: " + event.getFailureRate()))
            .onCallNotPermitted(event -&gt; 
                System.out.println("Call not permitted"))
            .onError(event -&gt; 
                System.out.println("Error: " + event.getThrowable().getMessage()));
    
    return circuitBreaker;
}

}

6.5. Sử dụng với WebClient (Reactive)

package com.example.order.service;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import io.github.resilience4j.reactor.circuitbreaker.operator.CircuitBreakerOperator; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono;

@Service @RequiredArgsConstructor @Slf4j public class PaymentServiceReactiveClient {

private final WebClient webClient;
private final io.github.resilience4j.circuitbreaker.CircuitBreaker circuitBreaker;

/**
 * Cách 1: Sử dụng annotation
 */
@CircuitBreaker(name = "paymentService", fallbackMethod = "processPaymentFallback")
public Mono&lt;PaymentResponse&gt; processPayment(PaymentRequest request) {
    return webClient.post()
            .uri("/api/payments")
            .bodyValue(request)
            .retrieve()
            .bodyToMono(PaymentResponse.class)
            .doOnSuccess(response -&gt; log.info("Payment successful: {}", response))
            .doOnError(error -&gt; log.error("Payment failed: {}", error.getMessage()));
}

public Mono&lt;PaymentResponse&gt; processPaymentFallback(PaymentRequest request, Throwable t) {
    log.warn("Fallback triggered for payment: {}", request.getOrderId());
    return Mono.just(PaymentResponse.builder()
            .orderId(request.getOrderId())
            .status("PENDING")
            .fallback(true)
            .build());
}

/**
 * Cách 2: Sử dụng operator programmatically
 */
public Mono&lt;PaymentResponse&gt; processPaymentWithOperator(PaymentRequest request) {
    return webClient.post()
            .uri("/api/payments")
            .bodyValue(request)
            .retrieve()
            .bodyToMono(PaymentResponse.class)
            .transformDeferred(CircuitBreakerOperator.of(circuitBreaker))
            .onErrorResume(throwable -&gt; {
                log.warn("Circuit breaker triggered fallback");
                return Mono.just(PaymentResponse.builder()
                        .status("PENDING")
                        .fallback(true)
                        .build());
            });
}

}

6.6. REST Controller với Circuit Breaker Status

package com.example.order.controller;

import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*;

import java.util.HashMap; import java.util.Map;

@RestController @RequestMapping("/api/orders") @RequiredArgsConstructor public class OrderController {

private final OrderService orderService;
private final CircuitBreakerRegistry circuitBreakerRegistry;

@PostMapping
public ResponseEntity&lt;OrderResponse&gt; createOrder(@RequestBody OrderRequest request) {
    OrderResponse response = orderService.createOrder(request);
    return ResponseEntity.ok(response);
}

/**
 * Endpoint kiểm tra trạng thái Circuit Breakers
 */
@GetMapping("/circuit-breakers/status")
public ResponseEntity&lt;Map&lt;String, Object&gt;&gt; getCircuitBreakersStatus() {
    Map&lt;String, Object&gt; status = new HashMap&lt;&gt;();
    
    circuitBreakerRegistry.getAllCircuitBreakers().forEach(cb -&gt; {
        CircuitBreaker.Metrics metrics = cb.getMetrics();
        Map&lt;String, Object&gt; cbStatus = new HashMap&lt;&gt;();
        cbStatus.put("state", cb.getState().name());
        cbStatus.put("failureRate", metrics.getFailureRate());
        cbStatus.put("slowCallRate", metrics.getSlowCallRate());
        cbStatus.put("bufferedCalls", metrics.getNumberOfBufferedCalls());
        cbStatus.put("failedCalls", metrics.getNumberOfFailedCalls());
        cbStatus.put("successfulCalls", metrics.getNumberOfSuccessfulCalls());
        cbStatus.put("notPermittedCalls", metrics.getNumberOfNotPermittedCalls());
        
        status.put(cb.getName(), cbStatus);
    });
    
    return ResponseEntity.ok(status);
}

/**
 * Endpoint để manually transition Circuit Breaker
 * (Chỉ dùng cho testing/debugging)
 */
@PostMapping("/circuit-breakers/{name}/transition/{state}")
public ResponseEntity&lt;String&gt; transitionCircuitBreaker(
        @PathVariable String name,
        @PathVariable String state) {
    
    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(name);
    
    switch (state.toUpperCase()) {
        case "OPEN":
            circuitBreaker.transitionToOpenState();
            break;
        case "CLOSED":
            circuitBreaker.transitionToClosedState();
            break;
        case "HALF_OPEN":
            circuitBreaker.transitionToHalfOpenState();
            break;
        default:
            return ResponseEntity.badRequest().body("Invalid state");
    }
    
    return ResponseEntity.ok("Transitioned to " + state);
}

}


7. Khi nào nên áp dụng Circuit Breaker?

7.1. NÊN sử dụng Circuit Breaker khi:

✅ Gọi External Services

// Gọi Payment Gateway (Stripe, VNPay, MoMo)
@CircuitBreaker(name = "paymentGateway")
public PaymentResult processPayment(PaymentRequest request) {
return paymentGatewayClient.charge(request);
}

// Gọi SMS/Email Provider @CircuitBreaker(name = "notificationService") public void sendNotification(NotificationRequest request) { twilioClient.sendSMS(request); }

✅ Giao tiếp giữa Microservices

// Order Service → Inventory Service
@CircuitBreaker(name = "inventoryService")
public InventoryStatus checkStock(String productId) {
return inventoryClient.getStock(productId);
}

// User Service → Auth Service @CircuitBreaker(name = "authService") public TokenInfo validateToken(String token) { return authClient.validate(token); }

✅ Truy cập Database qua Network

// Database cluster có thể unavailable
@CircuitBreaker(name = "databaseOperation")
public List<Order> getOrderHistory(String customerId) {
return orderRepository.findByCustomerId(customerId);
}

✅ Gọi Third-party APIs

// Weather API, Exchange Rate API, Social Login
@CircuitBreaker(name = "weatherApi")
public WeatherData getCurrentWeather(String location) {
return weatherApiClient.fetch(location);
}

7.2. KHÔNG NÊN sử dụng Circuit Breaker khi:

❌ Operations nội bộ không có network call

// Tính toán local - KHÔNG cần Circuit Breaker
public BigDecimal calculateDiscount(Order order) {
return order.getTotal().multiply(DISCOUNT_RATE);
}

// Validate input - KHÔNG cần Circuit Breaker public boolean validateEmail(String email) { return EMAIL_PATTERN.matcher(email).matches(); }

❌ Đã có timeout và retry đủ tốt

// Nếu đã có cơ chế retry với exponential backoff
// và timeout hợp lý, có thể không cần thêm Circuit Breaker

❌ Critical operations không thể fail

// Ví dụ: Ghi log audit bắt buộc
// Không nên dùng Circuit Breaker vì không thể skip

7.3. Decision Matrix

Tình huống Circuit Breaker? Lý do
Gọi Payment Service ✅ Có External service, có thể down
Gọi internal cache ❌ Không Local, không có network latency
Gọi Database primary ⚠️ Cân nhắc Tùy setup, thường có connection pool
Gọi Message Queue ✅ Có Network call, có thể timeout
Đọc file local ❌ Không I/O local, không cần
Gọi OAuth Provider ✅ Có External service, critical
Tính toán CPU-intensive ❌ Không Không phải network issue

7.4. Kết hợp với các Pattern khác

# Thứ tự áp dụng (từ ngoài vào trong):
# Retry -> CircuitBreaker -> RateLimiter -> TimeLimiter -> Bulkhead

resilience4j: retry: instances: paymentService: maxAttempts: 3 waitDuration: 1s retryExceptions: - java.io.IOException

circuitbreaker: instances: paymentService: failureRateThreshold: 50 waitDurationInOpenState: 30s

ratelimiter: instances: paymentService: limitForPeriod: 100 limitRefreshPeriod: 1s

timelimiter: instances: paymentService: timeoutDuration: 5s

bulkhead: instances: paymentService: maxConcurrentCalls: 10 maxWaitDuration: 500ms


8. Monitoring và Observability

8.1. Actuator Endpoints

# application.yml
management:
endpoints:
web:
exposure:
include: health,circuitbreakers,circuitbreakerevents
health:
circuitbreakers:
enabled: true
endpoint:
health:
show-details: always

Các endpoints có sẵn:

# Xem tất cả circuit breakers
GET /actuator/circuitbreakers

Xem events của circuit breaker cụ thể

GET /actuator/circuitbreakerevents GET /actuator/circuitbreakerevents/{name}

Health check bao gồm circuit breaker status

GET /actuator/health

Response mẫu:

// GET /actuator/circuitbreakers
{
"circuitBreakers": {
"paymentService": {
"failureRate": "25.0%",
"slowCallRate": "10.0%",
"failureRateThreshold": "50.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 20,
"failedCalls": 5,
"slowCalls": 2,
"slowFailedCalls": 1,
"notPermittedCalls": 0,
"state": "CLOSED"
}
}
}

8.2. Tích hợp Prometheus + Grafana

<!-- Thêm dependency -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
# application.yml
management:
endpoints:
web:
exposure:
include: health,prometheus,circuitbreakers
prometheus:
metrics:
export:
enabled: true

Metrics có sẵn:

# Số lượng calls theo trạng thái
resilience4j_circuitbreaker_calls_total{name="paymentService",kind="successful"} 150
resilience4j_circuitbreaker_calls_total{name="paymentService",kind="failed"} 10
resilience4j_circuitbreaker_calls_total{name="paymentService",kind="not_permitted"} 5

Trạng thái circuit breaker (0=CLOSED, 1=OPEN, 2=HALF_OPEN)

resilience4j_circuitbreaker_state{name="paymentService",state="closed"} 1

Failure rate

resilience4j_circuitbreaker_failure_rate{name="paymentService"} 6.25

Slow call rate

resilience4j_circuitbreaker_slow_call_rate{name="paymentService"} 2.5

8.3. Custom Event Handler

package com.example.order.config;

import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import io.github.resilience4j.circuitbreaker.event.*; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration;

import jakarta.annotation.PostConstruct;

@Configuration @Slf4j public class CircuitBreakerEventConfig {

private final CircuitBreakerRegistry registry;
private final AlertService alertService;

public CircuitBreakerEventConfig(CircuitBreakerRegistry registry, 
                                  AlertService alertService) {
    this.registry = registry;
    this.alertService = alertService;
}

@PostConstruct
public void registerEventConsumers() {
    registry.getAllCircuitBreakers().forEach(this::registerEventConsumer);
    
    // Đăng ký cho các circuit breakers được tạo sau
    registry.getEventPublisher()
            .onEntryAdded(event -&gt; registerEventConsumer(event.getAddedEntry()));
}

private void registerEventConsumer(CircuitBreaker circuitBreaker) {
    circuitBreaker.getEventPublisher()
            .onStateTransition(this::handleStateTransition)
            .onFailureRateExceeded(this::handleFailureRateExceeded)
            .onCallNotPermitted(this::handleCallNotPermitted)
            .onError(this::handleError)
            .onSuccess(this::handleSuccess);
}

private void handleStateTransition(CircuitBreakerOnStateTransitionEvent event) {
    String message = String.format(
        "Circuit Breaker '%s' transitioned from %s to %s",
        event.getCircuitBreakerName(),
        event.getStateTransition().getFromState(),
        event.getStateTransition().getToState()
    );
    
    log.warn(message);
    
    // Gửi alert khi chuyển sang OPEN
    if (event.getStateTransition().getToState() == CircuitBreaker.State.OPEN) {
        alertService.sendAlert(
            AlertLevel.HIGH,
            "Circuit Breaker OPENED",
            message
        );
    }
    
    // Gửi notification khi recovery (về CLOSED)
    if (event.getStateTransition().getToState() == CircuitBreaker.State.CLOSED 
        &amp;&amp; event.getStateTransition().getFromState() == CircuitBreaker.State.HALF_OPEN) {
        alertService.sendNotification(
            "Circuit Breaker RECOVERED",
            message
        );
    }
}

private void handleFailureRateExceeded(CircuitBreakerOnFailureRateExceededEvent event) {
    log.error("Failure rate exceeded for '{}': {}%", 
        event.getCircuitBreakerName(), 
        event.getFailureRate());
}

private void handleCallNotPermitted(CircuitBreakerOnCallNotPermittedEvent event) {
    log.debug("Call not permitted for '{}'", event.getCircuitBreakerName());
}

private void handleError(CircuitBreakerOnErrorEvent event) {
    log.error("Error in '{}': {}", 
        event.getCircuitBreakerName(), 
        event.getThrowable().getMessage());
}

private void handleSuccess(CircuitBreakerOnSuccessEvent event) {
    log.trace("Successful call to '{}' in {}ms", 
        event.getCircuitBreakerName(),
        event.getElapsedDuration().toMillis());
}

}


9. Best Practices

9.1. Thiết kế Fallback Strategy

/**

  • Các chiến lược Fallback phổ biến */ public class FallbackStrategies {

    // 1. Trả về giá trị mặc định public ProductInfo getProductFallback(String productId, Throwable t) { return ProductInfo.builder() .id(productId) .name("Product information temporarily unavailable") .available(false) .build(); }

    // 2. Trả về dữ liệu từ cache @Autowired private CacheManager cacheManager;

    public ProductInfo getProductFromCacheFallback(String productId, Throwable t) { Cache cache = cacheManager.getCache("products"); ProductInfo cached = cache.get(productId, ProductInfo.class);

     if (cached != null) {
         cached.setFromCache(true);
         return cached;
     }
     
     return getProductFallback(productId, t);
    

    }

    // 3. Gọi service backup public ProductInfo getProductFromBackupServiceFallback(String productId, Throwable t) { try { return backupProductService.getProduct(productId); } catch (Exception e) { return getProductFromCacheFallback(productId, t); } }

    // 4. Queue để xử lý sau (async fallback) @Autowired private RabbitTemplate rabbitTemplate;

    public OrderResponse createOrderAsyncFallback(OrderRequest request, Throwable t) { // Lưu vào queue để xử lý sau rabbitTemplate.convertAndSend("order-retry-queue", request);

     return OrderResponse.builder()
             .status("QUEUED")
             .message("Your order is being processed")
             .estimatedProcessingTime("5 minutes")
             .build();
    

    }

    // 5. Graceful degradation - giảm tính năng public RecommendationResponse getRecommendationsFallback(String userId, Throwable t) { // Thay vì personalized recommendations, trả về popular items return RecommendationResponse.builder() .items(popularItemsCache.getTopItems(10)) .type("POPULAR") // Thay vì "PERSONALIZED" .message("Showing popular items") .build(); } }

9.2. Tuning Parameters

# Development/Testing environment
resilience4j:
circuitbreaker:
configs:
development:
slidingWindowSize: 5           # Window nhỏ để test nhanh
minimumNumberOfCalls: 3        # Ít calls để trigger sớm
failureRateThreshold: 50
waitDurationInOpenState: 10s   # Recovery nhanh
permittedNumberOfCallsInHalfOpenState: 2

Production environment

resilience4j: circuitbreaker: configs: production: slidingWindowSize: 100 # Window lớn hơn, ổn định hơn minimumNumberOfCalls: 20 # Cần nhiều data points failureRateThreshold: 50 slowCallRateThreshold: 80 # Theo dõi slow calls slowCallDurationThreshold: 3s waitDurationInOpenState: 60s # Chờ lâu hơn permittedNumberOfCallsInHalfOpenState: 10

9.3. Testing Circuit Breaker

package com.example.order.service;

import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean;

import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*;

@SpringBootTest class PaymentServiceClientTest {

@Autowired
private PaymentServiceClient paymentClient;

@Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;

@MockBean
private RestTemplate restTemplate;

private CircuitBreaker circuitBreaker;

@BeforeEach
void setUp() {
    circuitBreaker = circuitBreakerRegistry.circuitBreaker("paymentService");
    circuitBreaker.reset(); // Reset state before each test
}

@Test
void shouldReturnSuccessfulResponse() {
    // Given
    PaymentResponse expectedResponse = PaymentResponse.builder()
            .status("SUCCESS")
            .build();
    when(restTemplate.postForObject(any(), any(), any()))
            .thenReturn(expectedResponse);
    
    // When
    PaymentResponse response = paymentClient.processPayment(
            PaymentRequest.builder().orderId("123").build()
    ).join();
    
    // Then
    assertThat(response.getStatus()).isEqualTo("SUCCESS");
    assertThat(response.isFallback()).isFalse();
    assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED);
}

@Test
void shouldOpenCircuitAfterFailures() {
    // Given - Circuit breaker có threshold 50%, window size 10
    when(restTemplate.postForObject(any(), any(), any()))
            .thenThrow(new RuntimeException("Service unavailable"));
    
    // When - Gọi nhiều lần để trigger circuit breaker
    for (int i = 0; i &lt; 10; i++) {
        try {
            paymentClient.processPayment(
                    PaymentRequest.builder().orderId("123").build()
            ).join();
        } catch (Exception ignored) {
        }
    }
    
    // Then
    assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN);
}

@Test
void shouldReturnFallbackWhenCircuitOpen() {
    // Given
    circuitBreaker.transitionToOpenState();
    
    // When
    PaymentResponse response = paymentClient.processPayment(
            PaymentRequest.builder().orderId("123").build()
    ).join();
    
    // Then
    assertThat(response.isFallback()).isTrue();
    assertThat(response.getStatus()).isEqualTo("PENDING");
    verify(restTemplate, never()).postForObject(any(), any(), any());
}

@Test
void shouldTransitionToHalfOpenAfterWaitDuration() throws InterruptedException {
    // Given
    circuitBreaker.transitionToOpenState();
    
    // When - Chờ hết waitDurationInOpenState (đã config là 10s trong test profile)
    Thread.sleep(11000);
    
    // Then
    assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.HALF_OPEN);
}

@Test
void shouldCloseAfterSuccessfulCallsInHalfOpen() {
    // Given
    circuitBreaker.transitionToHalfOpenState();
    when(restTemplate.postForObject(any(), any(), any()))
            .thenReturn(PaymentResponse.builder().status("SUCCESS").build());
    
    // When - Số calls thành công &gt;= permittedNumberOfCallsInHalfOpenState
    for (int i = 0; i &lt; 3; i++) {
        paymentClient.processPayment(
                PaymentRequest.builder().orderId("123").build()
        ).join();
    }
    
    // Then
    assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED);
}

}

9.4. Common Mistakes to Avoid

/**

  • ❌ SAI: Fallback method signature không đúng */ @CircuitBreaker(name = "service", fallbackMethod = "fallback") public String callService(String param) { return service.call(param); }

// ❌ Thiếu Throwable parameter public String fallback(String param) { // Sẽ không được gọi! return "default"; }

// ✅ ĐÚNG: Phải có Throwable/Exception public String fallback(String param, Throwable t) { return "default"; }

/**

  • ❌ SAI: Catch exception trong method */ @CircuitBreaker(name = "service") public String callService() { try { return service.call(); } catch (Exception e) { return "error"; // Circuit Breaker không thấy failure! } }

// ✅ ĐÚNG: Để exception propagate @CircuitBreaker(name = "service", fallbackMethod = "fallback") public String callService() { return service.call(); // Throw exception nếu fail }

/**

  • ❌ SAI: Self-invocation (gọi method trong cùng class) */ @Service public class MyService {

    @CircuitBreaker(name = "service") public String callService() { return externalService.call(); }

    public String doSomething() { return callService(); // Circuit Breaker không work! } }

// ✅ ĐÚNG: Gọi từ class khác hoặc inject self @Service public class MyService {

@Autowired
private MyService self;  // Hoặc inject từ ApplicationContext

@CircuitBreaker(name = "service")
public String callService() {
    return externalService.call();
}

public String doSomething() {
    return self.callService();  // Đi qua proxy, Circuit Breaker work!
}

}

/**

  • ❌ SAI: Không set timeout, leading to thread exhaustion */ @CircuitBreaker(name = "service") public String callSlowService() { return slowService.call(); // Có thể block 60s! }

// ✅ ĐÚNG: Kết hợp với TimeLimiter @CircuitBreaker(name = "service") @TimeLimiter(name = "service") // Timeout sau 5s public CompletableFuture<String> callSlowService() { return CompletableFuture.supplyAsync(() -> slowService.call()); }


10. Kết luận

10.1. Tóm tắt

Circuit Breaker là một pattern không thể thiếu trong kiến trúc microservices để:

  1. Ngăn chặn Cascading Failures - Bảo vệ hệ thống khỏi hiệu ứng domino
  2. Fail Fast - Trả về lỗi ngay thay vì chờ timeout
  3. Self-Healing - Tự động phục hồi khi dependency service hoạt động lại
  4. Graceful Degradation - Cung cấp fallback thay vì lỗi hoàn toàn

10.2. Checklist triển khai

  • [ ] Xác định các external dependencies cần bảo vệ
  • [ ] Thêm Resilience4j dependencies
  • [ ] Cấu hình circuit breaker parameters phù hợp
  • [ ] Implement fallback methods cho mọi circuit breaker
  • [ ] Thêm monitoring với Actuator + Prometheus
  • [ ] Setup alerting cho state transitions
  • [ ] Viết unit tests cho circuit breaker behavior
  • [ ] Tuning parameters dựa trên production metrics

10.3. Resources


Bài viết được viết cho series Spring Boot Advanced - Phù hợp cho backend developers và DevOps engineers muốn xây dựng hệ thống microservices resilient.

DUY TRAN
Tác giả

DUY TRAN

Pursuing an AI-first mindset and intelligent system architecture. I build solutions by combining technology, creativity, and the ability to see structure in chaos — the foundation for becoming a Solution Architect.

Bình luận

Bài viết liên quan