Vấn đề thực tế: Khi một dịch vụ “hắt hơi”, cả hệ thống “nằm sàn”
Hệ thống microservices đang chạy êm ru thì bỗng dưng lăn đùng ra chết hàng loạt lúc 2 giờ sáng. Log cho thấy server chính bị nghẽn sạch thread, không thể nhận thêm bất kỳ request nào. Sau một hồi toát mồ hôi hột debug, mình phát hiện thủ phạm: Một API thanh toán của đối tác bị chậm. Code của mình vẫn miệt mài gửi request rồi đứng đợi (wait) suốt 30 giây cho mỗi lượt cho đến khi timeout.
Khi API đối tác phản hồi chậm, các request phía mình bị dồn ứ như tắc đường giờ cao điểm. Mỗi request chiếm dụng một lượng tài nguyên RAM và CPU nhất định. Chỉ cần 100 request bị kẹt cùng lúc, toàn bộ ứng dụng Python sẽ cạn kiệt tài nguyên và sập nguồn. Đây chính là Cascading Failure (Lỗi dây chuyền) – cơn ác mộng thực sự trong hệ thống phân tán.
Tại sao “Timeout” và “Retry” là chưa đủ?
Nhiều dev thường tặc lưỡi: “Thì set timeout ngắn lại là xong!”. Nhưng đời không đơn giản thế. Nếu dịch vụ đích đã “ngỏm” hoặc đang quá tải trầm trọng, việc bạn cố đấm ăn xôi gửi thêm request chỉ làm mọi chuyện tệ hơn:
- Bồi thêm nhát dao cho dịch vụ đang lỗi: Dịch vụ kia đã yếu, bạn lại nã thêm hàng nghìn request mỗi giây thì nó sẽ không bao giờ hồi phục nổi.
- Lãng phí tài nguyên vô ích: Dù timeout chỉ 1 giây, bạn vẫn mất công thiết lập kết nối và xử lý ngoại lệ liên tục.
- Trải nghiệm người dùng cực tệ: Thay vì báo lỗi ngay, bạn bắt khách hàng nhìn màn hình loading xoay vòng 5-10 giây rồi mới báo thất bại.
Đó là lý do bạn cần đến Circuit Breaker Pattern – một “cầu chì” thông minh cho phần mềm.
Giải pháp: Circuit Breaker – Cầu chì bảo vệ code
Cơ chế này hoạt động y hệt cầu chì trong nhà. Khi dòng điện quá tải, cầu chì tự ngắt để bảo vệ tivi, tủ lạnh. Trong phần mềm, Circuit Breaker theo dõi các lần gọi API. Nếu tỉ lệ lỗi vượt ngưỡng, nó sẽ “ngắt mạch” (Open) ngay lập tức. Mọi yêu cầu gọi đến API đó sẽ bị từ chối thẳng thừng mà không cần gửi đi thực tế.
Circuit Breaker xoay quanh 3 trạng thái:
- Closed (Đóng mạch): Trạng thái bình thường. Mọi request đều đi qua. Nếu lỗi xảy ra, bộ đếm lỗi sẽ tăng dần.
- Open (Ngắt mạch): Khi lỗi đạt ngưỡng (ví dụ 5 lỗi liên tiếp), mạch ngắt. Request bị chặn lại và báo lỗi ngay lập tức.
- Half-Open (Nửa đóng nửa mở): Sau một khoảng thời gian chờ (ví dụ 30 giây), hệ thống cho phép vài request đi qua để “thăm dò”. Nếu thành công, mạch đóng lại. Nếu vẫn lỗi, mạch lại ngắt tiếp.
Triển khai nhanh với thư viện circuitbreaker
Đừng tốn công tự viết logic đếm lỗi loằng ngoằng. Thư viện circuitbreaker trong Python rất nhẹ và cực kỳ dễ tích hợp vào Flask, FastAPI hay Django.
Cài đặt
pip install circuitbreaker
Sử dụng Decorator để bọc hàm gọi API
Giả sử bạn có hàm lấy tỉ giá hối đoái. Hãy bọc nó bằng @circuit. Đây là cách làm gọn gàng nhất cho các dự án thực tế.
import requests
from circuitbreaker import circuit
# failure_threshold: Ngắt mạch sau 3 lần lỗi liên tiếp
# recovery_timeout: Chờ 10 giây trước khi thử lại (Half-Open)
@circuit(failure_threshold=3, recovery_timeout=10)
def call_external_api():
response = requests.get("https://api.example.com/data", timeout=2)
response.raise_for_status()
return response.json()
# Thử nghiệm
for i in range(10):
try:
print(f"Lần gọi {i+1}:", end=" ")
call_external_api()
print("Thành công!")
except Exception as e:
print(f"Lỗi: {e}")
Ở lần gọi thứ 4, nếu 3 lần trước đã lỗi, hàm thậm chí không chạy. Thư viện sẽ ném ngay CircuitBreakerError trong vòng vài miligiây.
Tùy chỉnh cho các trường hợp phức tạp
Không phải lỗi nào cũng đáng để ngắt mạch. Lỗi 404 Not Found thường là do logic sai, không phải do server sập. Bạn chỉ nên ngắt mạch khi gặp 500 Internal Server Error hoặc ConnectTimeout.
from circuitbreaker import CircuitBreaker
class PaymentServiceBreaker(CircuitBreaker):
FAILURE_THRESHOLD = 5
RECOVERY_TIMEOUT = 60
EXPECTED_EXCEPTIONS = (requests.exceptions.ConnectTimeout, requests.exceptions.HTTPError)
@PaymentServiceBreaker()
def process_payment():
# Logic thanh toán
pass
Cơ chế dự phòng (Fallback Mechanism)
Khi mạch ngắt, đừng để ứng dụng hiện trang trắng. Hãy chuẩn bị phương án B, ví dụ như lấy dữ liệu từ Cache hoặc trả về giá trị mặc định.
from circuitbreaker import CircuitBreakerError
def get_product_price(product_id):
try:
return call_api_with_circuit(product_id)
except CircuitBreakerError:
# Mạch ngắt, lấy giá cũ từ Redis để app không chết
return redis_cache.get(f"price:{product_id}")
Kinh nghiệm “xương máu” khi vận hành
Sau nhiều lần triển khai thực tế, mình rút ra vài lưu ý quan trọng:
- Tránh đặt ngưỡng quá nhạy: Nếu để
failure_threshold=1, chỉ cần một cái lag mạng nhẹ là hệ thống của bạn đã “tự sát”. Ngưỡng 5-10 lỗi là con số an toàn cho hầu hết dịch vụ. - Bắt buộc phải có Monitoring: Bạn cần biết khi nào mạch bị ngắt. Hãy đẩy metric lên Grafana. Nếu mạch ngắt liên tục, đó là dấu hiệu đối tác đang gặp sự cố nghiêm trọng cần can trị thiệp thủ công.
- Circuit Breaker không thay thế Timeout: Luôn phải set
timeoutcho request. Circuit Breaker chỉ hoạt động dựa trên kết quả trả về, nó không thể tự ngắt một request đang bị treo vô hạn.
Áp dụng Circuit Breaker giúp code của bạn “lì lợm” hơn hẳn. Thay vì sập toàn bộ, hệ thống chỉ tạm ngắt các tính năng đang lỗi. Nếu bạn đang làm việc với API bên thứ ba, hãy tích hợp nó ngay để tối kê cao gối ngủ ngon nhé!

