Trong hệ phân tán, một service phía sau sập không chỉ hỏng chính nó — nó kéo theo cả hệ thống. Khi service B chết, mọi lời gọi từ service A tới B chờ timeout (vài giây), goroutine của A dồn ứ, A cạn tài nguyên, rồi service C gọi A cũng sập theo. Đây là sập dây chuyền (cascading failure). Circuit breaker là cầu dao chống điều này: theo dõi lỗi, và khi service B rõ ràng đang chết, nó ngắt — chặn ngay các lời gọi thay vì để chúng chờ timeout vô ích. Bài này dựng một circuit breaker ba trạng thái chạy được và đo thật giá trị fail-fast.
Ba trạng thái
Circuit breaker là một máy trạng thái ba pha, mô phỏng đúng cầu dao điện:
- Closed (đóng): bình thường, cho lời gọi qua, đếm lỗi liên tiếp.
- Open (mở): service đang lỗi, chặn ngay mọi lời gọi (fail-fast), không gọi service.
- HalfOpen (nửa mở): sau một thời gian nghỉ, cho vài lời gọi thăm dò xem service đã hồi phục chưa.
func (b *Breaker) Goi(fn func() error) error {
if b.state == Open {
if time.Since(b.moLuc) > b.nghi {
b.state = HalfOpen // hết nghỉ -> thử thăm dò
} else {
return ErrMo // CHẶN NGAY, không tốn thời gian chờ timeout
}
}
err := fn() // mới thực gọi
// ... cập nhật trạng thái theo err ...
}

Hình 1: Ba trạng thái Closed/Open/HalfOpen. Ở Open, Goi trả lỗi ngay không gọi service; hết thời gian nghỉ chuyển sang HalfOpen. Đếm lỗi liên tiếp: đủ ngưỡng thì mở; thành công thì reset về Closed.
Đo thật: chuyển trạng thái
Chạy thật (Go 1.23) với ngưỡng 3 lỗi, nghỉ 300ms:
--- Giai đoạn 1: service lỗi liên tục ---
lần 1: state=CLOSED err=service chết (đếm lỗi)
lần 2: state=CLOSED err=service chết
lần 3: state=OPEN err=service chết (đủ 3 lỗi -> MỞ)
lần 4: state=OPEN err=circuit breaker ĐANG MỞ (chặn nhanh)
lần 5: state=OPEN err=circuit breaker ĐANG MỞ (chặn nhanh)
--- Giai đoạn 2: chờ qua nghỉ, thử lại ---
sau nghỉ, gọi thành công: state=CLOSED (hồi phục)
Hai lỗi đầu vẫn CLOSED (đang đếm). Lỗi thứ 3 đạt ngưỡng → OPEN. Từ lần 4, breaker không gọi service nữa mà trả lỗi "chặn nhanh" luôn. Sau 300ms nghỉ, một lời gọi thành công đưa breaker về CLOSED — tự hồi phục.
Đo thật: fail-fast cứu hệ thống
Giá trị thật của circuit breaker lộ ra khi service chết chậm — mỗi lời gọi phải chờ timeout mới biết lỗi. Benchmark với service lỗi sau 50ms:

Hình 2: Chuyển trạng thái đúng — 3 lỗi → OPEN, lần 4-5 chặn nhanh, sau nghỉ hồi phục về CLOSED. Benchmark: không breaker chờ timeout 55.414.448 ns/lần so với breaker đã mở chặn nhanh 106,7 ns — nhanh hơn ~500.000 lần.
- không breaker (chờ timeout 50ms mỗi lần): 55.414.448 ns/op (~55ms).
- có breaker (đã Open, chặn nhanh): 106,7 ns/op.
Khi service chết, breaker mở trả lỗi trong ~107 ns thay vì kẹt 55ms chờ timeout — nhanh hơn ~500.000 lần. Con số khổng lồ này chính là điều cứu hệ thống: thay vì hàng nghìn goroutine kẹt chờ timeout của service chết (làm cạn tài nguyên và lan sập), breaker trả lỗi tức thì, giải phóng caller và cho service phía sau cơ hội nghỉ để hồi phục.
Đánh đổi cần cân nhắc
Chỉnh ngưỡng và thời gian nghỉ là nghệ thuật. Ngưỡng quá nhạy (mở sau 2 lỗi) gây "false trip" — chặn oan khi chỉ là trục trặc nhất thời. Ngưỡng quá cao (50 lỗi) thì breaker mở quá muộn, hệ thống đã dồn tải. Thời gian nghỉ quá ngắn dồn thăm dò vào service chưa hồi, quá dài thì phục vụ kém khi service đã khỏe. Thực tế nên dựa trên tỉ lệ lỗi trong cửa sổ (ví dụ >50% lỗi trong 10 giây) thay vì đếm liên tiếp thô như ví dụ này, và đo/điều chỉnh theo hành vi thật.
HalfOpen cần cẩn thận với số lời gọi thăm dò. Khi chuyển sang HalfOpen, nếu cho tất cả lời gọi đang chờ đi qua cùng lúc và service vẫn yếu, bạn lại dồn tải nó ngay. Breaker sản xuất giới hạn số lời gọi thăm dò (ví dụ chỉ 1-vài request), và chỉ đóng hẳn sau vài lần thành công liên tiếp — tránh mở/đóng dao động (flapping).
Circuit breaker là một phần của bộ công cụ resilience, không đứng một mình. Nó nên đi kèm timeout (để lời gọi có điểm dừng, mà breaker đếm được), retry với backoff (thử lại lỗi nhất thời — bài sau), và bulkhead (cô lập tài nguyên giữa các dependency). Dùng breaker mà không có timeout thì nó không biết khi nào "lỗi"; dùng riêng lẻ thì thiếu các lớp bảo vệ khác. Thư viện như sony/gobreaker cung cấp bản đã kiểm chứng cho production.
Ba ý mang về
- Circuit breaker là máy trạng thái ba pha: Closed (cho qua, đếm lỗi), Open (chặn nhanh khi service lỗi), HalfOpen (thăm dò sau nghỉ) — đo thật, 3 lỗi liên tiếp đưa breaker sang OPEN, các lời gọi sau bị chặn nhanh, và một lời gọi thành công sau thời gian nghỉ đưa về CLOSED (tự hồi phục).
- Giá trị cốt lõi là fail-fast: đo thật, khi service chết chậm (timeout 50ms), breaker đã mở trả lỗi trong 107 ns thay vì 55ms — nhanh hơn ~500.000 lần, cứu hệ thống khỏi dồn goroutine kẹt chờ timeout và sập dây chuyền.
- Cần chỉnh và ghép công cụ khác: ngưỡng/nghỉ phải cân (thực tế dùng tỉ lệ lỗi trong cửa sổ), HalfOpen giới hạn thăm dò tránh flapping, và circuit breaker phải đi cùng timeout + retry + bulkhead — dùng thư viện đã kiểm chứng cho production.
Phần sau ta xét lớp bảo vệ bổ trợ cho circuit breaker, xử lý lỗi nhất thời thông minh: Phần sau dựng retry với exponential backoff và jitter — thử lại lỗi tạm thời mà không gây "thundering herd" khi nhiều client cùng retry một lúc.