Circuit breaker: ngắt sớm để khỏi đợi một service đã chết
Tóm tắt
Circuit breaker đứng giữa bên gọi và service phụ thuộc. Khi số lỗi vượt ngưỡng, nó “ngắt mạch” và trả lỗi ngay cho các request sau thay vì để chúng chờ timeout. Sau một khoảng thời gian, nó cho thử vài request để xem service đã hồi phục chưa.
Một service phụ thuộc bắt đầu chậm hoặc chết. Mỗi request gọi tới nó đều chờ hết timeout (giả sử 1 giây) mới báo lỗi. Trong lúc đó các luồng, kết nối và bộ nhớ của bạn bị giữ lại, hàng đợi dài ra, và dịch vụ của bạn cũng chậm theo — một lỗi cục bộ lan thành sự cố dây chuyền. Circuit breaker (cầu dao tự ngắt) giải quyết bằng cách đứng giữa bên gọi và service phụ thuộc: khi số lỗi vượt ngưỡng, nó “ngắt mạch” và trả lỗi ngay cho các request sau thay vì để chúng chờ timeout.
Ba trạng thái
| Trạng thái | Hành vi | Chuyển sang |
|---|---|---|
| Closed | Request đi qua bình thường, breaker đếm lỗi. | Open khi đủ ngưỡng lỗi (ví dụ 5 lỗi) |
| Open | Trả lỗi ngay, không gọi service — service được yên để hồi phục. | Half-open sau thời gian chờ (ví dụ 3 giây) |
| Half-open | Cho 1 request thử đi qua. | Closed nếu thành công, Open lại nếu lỗi |
Trong video: suốt 3 giây ngắt mạch, 15 request nhận phản hồi ngay thay vì mỗi cái chờ 1000ms.
Vì sao có lợi?
- Thất bại nhanh: người dùng nhận phản hồi ngay thay vì chờ timeout.
- Bảo vệ tài nguyên của bạn: không giữ luồng/kết nối vào lời gọi chắc chắn thất bại.
- Cho service phụ thuộc cơ hội hồi phục: giảm tải trong lúc nó đang gặp vấn đề.
- Cô lập sự cố: ngăn lan toả lên các phần khác của hệ thống.
Mã ý tưởng
class Breaker {
state = 'closed'; fails = 0; openedAt = 0;
async call(fn) {
if (this.state === 'open') {
if (Date.now() - this.openedAt < 3000) throw new Error('circuit open');
this.state = 'half-open';
}
try { const r = await fn(); this.state = 'closed'; this.fails = 0; return r; }
catch (e) {
if (++this.fails >= 5 || this.state === 'half-open') { this.state = 'open'; this.openedAt = Date.now(); }
throw e;
}
}
}
Chọn tham số
- Ngưỡng lỗi: theo số lỗi liên tiếp hoặc tỉ lệ lỗi trong một cửa sổ thời gian; cần đủ lưu lượng để tỉ lệ có ý nghĩa.
- Thời gian mở: quá ngắn thì liên tục thử lại service chưa hồi phục; quá dài thì kéo dài thời gian không dùng được.
- Số request thử ở half-open: ít để tránh đè service đang hồi phục.
- Loại lỗi tính vào ngưỡng: chỉ lỗi hạ tầng/timeout, không phải lỗi nghiệp vụ như 404.
Áp dụng thế nào?
- Đặt breaker cho mọi lời gọi ra ngoài (API bên thứ ba, service nội bộ khác) để một service chết không kéo sập cả hệ thống.
- Có phương án dự phòng khi mạch mở: trả dữ liệu cache, giá trị mặc định hoặc thông báo thân thiện.
- Kết hợp với timeout ngắn và retry có jitter — breaker ngăn retry vô ích khi service đã chết.
- Theo dõi: cảnh báo khi breaker chuyển sang Open; đây là tín hiệu sự cố sớm.
Thư viện gợi ý: Resilience4j (Java), Polly (.NET), opossum (Node.js), hoặc service mesh như Istio.
Nguồn & tham khảo
- CircuitBreaker — Martin Fowler