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ố.
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:
- Order Service chờ response → threads bị block
- Requests đến Order Service tăng lên → thread pool cạn kiệt
- Order Service không thể xử lý request mới → cũng "chết"
- 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>
<!-- Resilience4j Circuit Breaker -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<!-- AOP cho annotations -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Actuator cho monitoring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</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 -> CircuitBreaker -> TimeLimiter -> Bulkhead
*/
@CircuitBreaker(name = "paymentService", fallbackMethod = "processPaymentFallback")
@Retry(name = "paymentService")
@TimeLimiter(name = "paymentService")
public CompletableFuture<PaymentResponse> processPayment(PaymentRequest request) {
log.info("Calling Payment Service for order: {}", request.getOrderId());
return CompletableFuture.supplyAsync(() -> {
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<PaymentResponse> 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 ->
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 ->
System.out.println("State transition: " + event.getStateTransition()))
.onFailureRateExceeded(event ->
System.out.println("Failure rate exceeded: " + event.getFailureRate()))
.onCallNotPermitted(event ->
System.out.println("Call not permitted"))
.onError(event ->
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<PaymentResponse> processPayment(PaymentRequest request) {
return webClient.post()
.uri("/api/payments")
.bodyValue(request)
.retrieve()
.bodyToMono(PaymentResponse.class)
.doOnSuccess(response -> log.info("Payment successful: {}", response))
.doOnError(error -> log.error("Payment failed: {}", error.getMessage()));
}
public Mono<PaymentResponse> 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<PaymentResponse> processPaymentWithOperator(PaymentRequest request) {
return webClient.post()
.uri("/api/payments")
.bodyValue(request)
.retrieve()
.bodyToMono(PaymentResponse.class)
.transformDeferred(CircuitBreakerOperator.of(circuitBreaker))
.onErrorResume(throwable -> {
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<OrderResponse> 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<Map<String, Object>> getCircuitBreakersStatus() {
Map<String, Object> status = new HashMap<>();
circuitBreakerRegistry.getAllCircuitBreakers().forEach(cb -> {
CircuitBreaker.Metrics metrics = cb.getMetrics();
Map<String, Object> cbStatus = new HashMap<>();
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<String> 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 -> 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
&& 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 < 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 >= permittedNumberOfCallsInHalfOpenState
for (int i = 0; i < 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 để:
- Ngăn chặn Cascading Failures - Bảo vệ hệ thống khỏi hiệu ứng domino
- Fail Fast - Trả về lỗi ngay thay vì chờ timeout
- Self-Healing - Tự động phục hồi khi dependency service hoạt động lại
- 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
- Resilience4j Official Documentation
- Spring Cloud Circuit Breaker
- Martin Fowler - Circuit Breaker
- Release It! - Michael Nygard
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.