Circuit Breaker Pattern — đừng để một service chết kéo sập cả hệ thống

Phong Hy

Hồi mới ra trường đi làm, mình từng chứng kiến một vụ sập hệ thống kinh điển: service A gọi service B, B gọi C, C gọi D. Đùng một cái, D chết vì quá tải — và chỉ trong vài phút, A, B, C cũng chết theo vì hết connection pool. Cả hệ thống ngưng hoạt động chỉ vì một service nhỏ nhất ngỏm.

Lúc đó mình chỉ biết restart cả đám và cầu trời nó đừng tái diễn. Nhưng về sau mình mới biết có một pattern xử lý chuyện này rất gọn: Circuit Breaker.

Server room với thiết bị mạng Ảnh: ranjeet . — Pexels

Circuit Breaker là gì?

Nói nôm na, Circuit Breaker là một lớp trung gian giữa client và service. Nó theo dõi số lần request thất bại. Khi lỗi vượt ngưỡng, nó ngắt mạch — không cho request nào đi tới service đang chết nữa, cho service đó thời gian hồi phục.

Ba trạng thái cơ bản:

  • Closed (đóng): Mọi request đi qua bình thường. Circuit breaker đếm lỗi.
  • Open (ngắt): Khi số lỗi vượt ngưỡng. Request bị từ chối ngay lập tức (fail fast).
  • Half-Open (nửa mở): Sau một thời gian, cho một request thử đi qua. Nếu thành công → đóng lại. Nếu thất bại → mở tiếp.

Code mẫu trong Go

Mình viết một circuit breaker đơn giản để anh em dễ hình dung:

package circuitbreaker

import (
	"sync"
	"time"
)

type State int

const (
	StateClosed State = iota
	StateOpen
	StateHalfOpen
)

type CircuitBreaker struct {
	mu              sync.RWMutex
	state           State
	failureCount    int
	threshold       int
	resetTimeout    time.Duration
	lastFailureTime time.Time
}

func New(threshold int, timeout time.Duration) *CircuitBreaker {
	return &CircuitBreaker{
		state:        StateClosed,
		threshold:    threshold,
		resetTimeout: timeout,
	}
}

func (cb *CircuitBreaker) Call(fn func() error) error {
	cb.mu.Lock()
	if cb.state == StateOpen {
		if time.Since(cb.lastFailureTime) > cb.resetTimeout {
			cb.state = StateHalfOpen
		} else {
			cb.mu.Unlock()
			return fmt.Errorf("circuit breaker is open")
		}
	}
	cb.mu.Unlock()

	err := fn()

	cb.mu.Lock()
	defer cb.mu.Unlock()

	if err != nil {
		cb.failureCount++
		cb.lastFailureTime = time.Now()
		if cb.failureCount >= cb.threshold {
			cb.state = StateOpen
		}
	} else {
		if cb.state == StateHalfOpen {
			cb.state = StateClosed
			cb.failureCount = 0
		}
	}
	return err
}

Đơn giản vậy thôi. Dùng nó:

cb := circuitbreaker.New(5, 30*time.Second)

err := cb.Call(func() error {
	resp, err := http.Get("http://dependency-service/api/data")
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 500 {
		return fmt.Errorf("server error: %d", resp.StatusCode)
	}
	return nil
})

Developer đang debug lỗi trên màn hình Ảnh: Rashed Paykary — Pexels

Kinh nghiệm thực tế

Mình đã deploy circuit breaker cho một hệ thống xử lý payment. Hồi đó mỗi lần payment gateway chậm, cả cụm service khác cũng ngừng theo vì hết goroutines, hết connections. Sau khi thêm circuit breaker:

  • Payment gateway timeout 30s → fail fast sau 3 lần thất bại liên tiếp
  • Các service khác không bị ảnh hưởng
  • Circuit breaker tự động reset sau 1 phút, payment gateway có thời gian hồi phục
  • Giảm ~70% cảnh báo lỗi liên quan tới timeout

Có một chuyện mình cũng rút ra: chọn threshold và reset timeout sao cho phù hợp. Nếu threshold quá thấp (2-3 lần), chỉ một lỗi nhỏ cũng mở breaker. Nếu quá cao (100+ lần), thì không khác gì không có.

Thường mình dùng:

  • Threshold: 5-10 lần cho internal service, cao hơn cho external API
  • Reset timeout: 10-30 giây cho internal, 30-60 giây cho external
  • Kết hợp với retry có exponential backoff nữa thì càng ngon

Khi nào không nên dùng?

Circuit breaker không phải cái gì cũng gắn vào được. Mấy trường hợp không nên dùng:

  • Local function call — không có network, lỗi là do code, không cần breaker
  • Idempotent operations — nếu retry không gây hại, breaker chỉ làm chậm thêm
  • Critical path quá nhạy cảm — như health check endpoint, không nên bị block bởi breaker

Developer team đang thảo luận Ảnh: Mikhail Nilov — Pexels

Tổng kết

Circuit Breaker là một pattern đơn giản nhưng cực kỳ hiệu quả cho microservices. Nó giúp hệ thống của bạn fail gracefully thay vì sập lan truyền. Kết hợp với retry, timeout, và bulkhead pattern nữa thì bạn có một hệ thống siêu chịu chơi.

Nếu bạn chưa dùng nó, hãy thử trong project tới. Thư viện như gobreaker (Go), resilience4j (Java), pybreaker (Python) có sẵn rồi — chỉ việc xài thôi.

📋 Phụ lục thuật ngữ

  • Circuit Breaker — pattern ngăn chặn request đến service đang lỗi, giúp hệ thống hồi phục trước khi gửi request lại
  • Cascade failure — lỗi lan truyền từ service này sang service khác
  • Fail fast — từ chối request ngay lập tức khi biết chắc sẽ thất bại
  • Threshold — ngưỡng số lần lỗi để mở circuit breaker
  • Reset timeout — thời gian chờ trước khi thử lại request