TungDaDev's Blog

Resilience4j trong Spring Boot: Làm chủ Circuit Breaker, Bulkhead và RateLimiter

Photo 1508739773434 c26b3d09e071?auto=format&fit=crop&w=1600&q=80
Published on
/7 mins read/

Trong hệ thống phân tán, mọi thứ đều có thể thất bại (Everything fails, all the time). Một kiến trúc kiên cường không phải là kiến trúc không bao giờ lỗi, mà là hệ thống biết cách cô lập sự cố để tiếp tục phục vụ người dùng một cách thanh nhã (Graceful Degradation).

1. Hiệu ứng sập dây chuyền (Cascading Failure)

Hãy tưởng tượng một kịch bản thực tế trong hệ thống E-commerce:

  1. OrderService gọi PaymentGatewayService để thanh toán hóa đơn.
  2. Cổng thanh toán bên thứ ba gặp sự cố, thay vì trả về lỗi trong 100ms, nó bị treo và mất 30 giây mới timeout.
  3. Mỗi request từ người dùng gửi đến OrderService chiếm dụng 1 Tomcat Worker Thread trong 30 giây.
  4. Chỉ sau 2 phút, toàn bộ 200 luồng trong Tomcat Thread Pool của OrderService bị cạn kiệt (Thread Exhaustion).
  5. Kể từ lúc này, ngay cả các API đọc danh sách đơn hàng hay xem giỏ hàng của OrderService (vốn không hề liên quan đến thanh toán) cũng bị từ chối phục vụ (HTTP 503).
  6. Một service bên ngoài bị chậm đã kéo sập toàn bộ hệ thống lõi!

Để ngăn chặn thảm họa này, Circuit Breaker (Cầu dao tự động) và Bulkhead (Vách ngăn cách ly) là những mẫu hình sống còn. Và Resilience4j hiện là chuẩn mực số một của hệ sinh thái Java/Spring Boot (thay thế cho Netflix Hystrix đã bị deprecated).


2. Máy trạng thái của Circuit Breaker trong Resilience4j

Khác với cầu dao điện chỉ có bật và tắt, Circuit Breaker trong phần mềm hoạt động như một Finite State Machine (FSM) gồm 3 trạng thái chính:

  1. CLOSED (Đóng mạch - Bình thường):
    • Mọi request được chuyển tiếp đến downstream service bình thường.
    • Resilience4j theo dõi tỷ lệ thất bại trong một cửa sổ trượt (Sliding Window).
  2. OPEN (Ngắt mạch - Bật cầu dao):
    • Khi tỷ lệ lỗi hoặc số request chậm vượt ngưỡng (ví dụ > 50%), cầu dao tự ngắt.
    • Tất cả request tiếp theo bị chặn đứng ngay lập tức tại cổng và trả về lỗi CallNotPermittedException (hoặc chuyển sang Fallback) mà không hề gọi mạng sang downstream service.
    • Điều này giúp downstream service có thời gian phục hồi và không làm nghẽn luồng của caller.
  3. HALF-OPEN (Nửa mở - Thử nghiệm phục hồi):
    • Sau một khoảng thời gian cấu hình sẵn (ví dụ 10 giây), cầu dao chuyển sang trạng thái nửa mở.
    • Nó cho phép một số lượng request giới hạn đi qua để thăm dò sức khỏe downstream.
    • Nếu thành công: Cầu dao đóng lại (CLOSED). Nếu vẫn lỗi: Cầu dao ngắt tiếp (OPEN).

3. Cấu hình Circuit Breaker thực chiến với Spring Boot 3

3.1. Khai báo Dependency

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>2.2.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

3.2. Cấu hình chi tiết trong application.yml

Resilience4j hỗ trợ 2 loại Sliding Window: COUNT_BASED (theo số lượng request) và TIME_BASED (theo khoảng thời gian).

resilience4j:
  circuitbreaker:
    instances:
      paymentGateway:
        sliding-window-type: COUNT_BASED
        sliding-window-size: 20 # Quan sát 20 request gần nhất
        minimum-number-of-calls: 10 # Phải có ít nhất 10 request mới bắt đầu tính tỷ lệ
        failure-rate-threshold: 50 # Nếu lỗi >= 50% -> Ngắt cầu dao
        slow-call-rate-threshold: 70 # Nếu 70% request chậm -> Ngắt cầu dao
        slow-call-duration-threshold: 2s # Request > 2 giây được coi là chậm
        wait-duration-in-open-state: 15s # Chờ 15s trước khi chuyển sang HALF_OPEN
        permitted-number-of-calls-in-half-open-state: 5 # Thử 5 request lúc Half-Open
        automatic-transition-from-open-to-half-open-enabled: true

3.3. Áp dụng Annotation và hàm Fallback

@Service
@Slf4j
public class PaymentClient {
 
    private final RestClient restClient;
 
    public PaymentClient(RestClient.Builder builder) {
        this.restClient = builder.baseUrl("https://api.payment-gateway.com").build();
    }
 
    @CircuitBreaker(name = "paymentGateway", fallbackMethod = "fallbackProcessPayment")
    public PaymentResponse charge(PaymentRequest req) {
        return restClient.post()
            .uri("/v1/charge")
            .body(req)
            .retrieve()
            .body(PaymentResponse.class);
    }
 
    // Hàm Fallback phải có cùng tham số truyền vào + thêm Throwable ở cuối
    public PaymentResponse fallbackProcessPayment(PaymentRequest req, Throwable t) {
        log.error("Cổng thanh toán lỗi hoặc Cầu dao đang OPEN. Kích hoạt fallback: {}", t.getMessage());
        return PaymentResponse.builder()
            .status("PENDING_OFFLINE")
            .message("Giao dịch đang được tạm giữ để xử lý đối soát sau")
            .build();
    }
}

4. Bulkhead: Vách ngăn chống chìm tàu

Thuật ngữ Bulkhead lấy cảm hứng từ các vách ngăn kín nước trên tàu thủy. Nếu thân tàu bị thủng một khoang, nước chỉ tràn vào khoang đó mà không làm chìm toàn bộ con tàu.

Trong phần mềm, Bulkhead giới hạn số lượng tài nguyên (luồng hoặc concurrent calls) mà một tác vụ cụ thể được phép tiêu thụ:

Resilience4j cung cấp 2 cơ chế Bulkhead:

  1. SemaphoreBulkhead: Giới hạn số lượng concurrent requests (chạy trên cùng luồng hiện tại).
  2. ThreadPoolBulkhead: Chạy request trên một ThreadPool độc lập với hàng đợi (Queue) riêng.
resilience4j:
  bulkhead:
    instances:
      paymentGateway:
        max-concurrent-calls: 15 # Tối đa 15 request đồng thời vào Payment, request thứ 16 sẽ bị từ chối ngay
        max-wait-duration: 20ms # Chỉ chờ 20ms nếu hết slot

5. RateLimiter: Kiểm soát lưu lượng request

Bảo vệ service của bạn khỏi bị quá tải bởi các đợt bùng nổ traffic hoặc các đợt crawl dữ liệu:

resilience4j:
  ratelimiter:
    instances:
      publicApi:
        limit-for-period: 100 # Tối đa 100 requests
        limit-refresh-period: 1s # Mỗi 1 giây
        timeout-duration: 0ms # Không chờ, reject ngay nếu vượt

Áp dụng trong code:

@RateLimiter(name = "publicApi")
@GetMapping("/products/{id}")
public Product getProduct(@PathVariable Long id) {
    return productService.findById(id);
}

6. Thứ tự kết hợp các mẫu hình (Decorator Execution Order)

Khi bạn khai báo nhiều annotation trên cùng một method, thứ tự thực thi mặc định của Resilience4j từ ngoài vào trong là:

  1. Retry: Thử lại nếu gặp lỗi tạm thời.
  2. CircuitBreaker: Ngắt mạch nếu tỷ lệ lỗi quá cao.
  3. RateLimiter: Kiểm soát tần suất gọi.
  4. TimeLimiter: Giới hạn thời gian chạy tối đa (timeout).
  5. Bulkhead: Giới hạn số luồng đồng thời.

7. Tổng kết

Một hệ thống phân tán được coi là hoàn thiện khi và chỉ khi nó có khả năng tự vệ trước những thất bại của các thành phần bên ngoài. Bằng việc kết hợp nhuần nhuyễn Circuit Breaker, Bulkhead, RateLimiter và Fallback thông qua thư viện Resilience4j, bạn sẽ biến các dịch vụ Spring Boot của mình trở thành một pháo đài bất khả xâm phạm trước mọi sự cố mạng và gián đoạn downstream.


Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè & đồng nghiệp nhé!

Happy coding 😎 👍🏻 🚀 🔥.