Ngăn chặn lỗi dây chuyền: Hướng dẫn thực hành Pattern Circuit Breaker

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

Phản ứng dây chuyền của các lỗi hệ thống

Các microservice hiếm khi hoạt động độc lập. Trong một luồng thương mại điện tử điển hình, Order Service có thể gọi Inventory Service, sau đó truy vấn một Warehouse Database cũ. Nếu cơ sở dữ liệu đó bị treo, Inventory Service sẽ chiếm dụng các worker threads trong khi chờ phản hồi. Chỉ trong vài giây, Order Service sẽ làm cạn kiệt thread pool gồm 200 kết nối của chính nó, và toàn bộ quá trình thanh toán sẽ bị đình trệ. Đây chính là lỗi dây chuyền (cascading failure).

Việc thử lại (retry) các yêu cầu bị lỗi một cách mù quáng thường làm mọi thứ tồi tệ hơn. Nếu một dịch vụ hạ nguồn (downstream) đang gặp khó khăn dưới tải trọng lớn, việc dồn thêm lưu lượng truy cập vào đó sẽ khiến nó không thể phục hồi. Pattern Circuit Breaker giải quyết vấn đề này bằng cách hoạt động như một cầu dao điện an toàn. Nó phát hiện khi một dịch vụ không khỏe mạnh và ngắt kết nối ngay lập tức để bảo vệ phần còn lại của kiến trúc.

Triển khai Circuit Breaker đầu tiên của bạn

Bạn có thể triển khai logic này mà không cần cơ sở hạ tầng phức tạp như service mesh. Các thư viện hiện đại sẽ xử lý việc quản lý trạng thái cho bạn. Dưới đây là một ví dụ thực tế sử dụng Python và thư viện circuitbreaker để bao bọc một lệnh gọi API bên ngoài không ổn định.

# pip install pycircuitbreaker

from circuitbreaker import circuit
import requests

# Ngắt mạch nếu 5 lần gọi liên tiếp thất bại
@circuit(failure_threshold=5, recovery_timeout=60)
def call_external_api():
    # Thiết lập timeout nghiêm ngặt để tránh treo thread
    response = requests.get("https://api.unreliable-service.com/data", timeout=0.5)
    response.raise_for_status()
    return response.json()

try:
    data = call_external_api()
except Exception:
    # Khối này thực thi nếu dịch vụ thất bại HOẶC mạch đã MỞ (OPEN)
    data = get_cached_backup_data()

Decorator này giám sát hàm. Nếu năm yêu cầu liên tiếp thất bại, mạch sẽ “ngắt” (trip). Trong 60 giây tiếp theo, mọi nỗ lực gọi hàm này sẽ trả về lỗi ngay lập tức. Thậm chí không có yêu cầu mạng nào được thực hiện. Điều này giúp dịch vụ đang gặp sự cố có trọn vẹn một phút “yên tĩnh” để phục hồi hoặc tự động mở rộng (auto-scale).

Ba trạng thái của khả năng phục hồi

Một bộ ngắt mạch hoạt động như một máy trạng thái (state machine). Hiểu cách nó chuyển đổi giữa ba giai đoạn này là chìa khóa để xử lý các sự cố trên môi trường production.

1. Trạng thái Đóng – Closed (Khỏe mạnh)

Các yêu cầu lưu thông bình thường. Bộ ngắt mạch theo dõi số lượng lỗi gần đây so với số lần thành công. Miễn là tỷ lệ lỗi của bạn nằm dưới ngưỡng—ví dụ: 5% tổng lưu lượng—hệ thống vẫn ở trạng thái này.

2. Trạng thái Mở – Open (Thất bại nhanh)

Khi đạt đến giới hạn thất bại, bộ ngắt mạch sẽ chuyển sang Open. Tất cả các lệnh gọi sẽ thất bại ngay lập tức. Cơ chế “fail-fast” này rất quan trọng vì nó ngăn ứng dụng của bạn lãng phí tài nguyên vào các yêu cầu gần như chắc chắn sẽ bị timeout.

3. Trạng thái Nửa mở – Half-Open (Thử nghiệm)

Sau thời gian chờ phục hồi (ví dụ: 30 hoặc 60 giây), bộ ngắt mạch sẽ chuyển sang Half-Open. Nó cho phép đúng một yêu cầu “thăm dò” đi qua. Nếu yêu cầu duy nhất đó thành công, bộ ngắt mạch sẽ reset về Closed. Nếu thất bại, bộ đếm thời gian sẽ khởi động lại và bộ ngắt mạch vẫn ở trạng thái Open.

Việc áp dụng pattern này đã thay đổi cách đội ngũ của chúng tôi xử lý các đợt tăng vọt lưu lượng. Thay vì toàn bộ nền tảng bị sập khi một công cụ gợi ý (recommendation engine) không thiết yếu gặp lỗi, mạch đã ngắt và giỏ hàng cốt lõi vẫn hoạt động bình thường.

Tinh chỉnh cho môi trường Production

Các bộ đếm cơ bản không phải lúc nào cũng đủ cho các môi trường có lưu lượng truy cập cao. Bạn cần tính đến khối lượng và loại lỗi đang xảy ra.

Sử dụng tỷ lệ phần trăm, không chỉ là số lượng

Ở mức 1.000 yêu cầu mỗi giây, năm lỗi là không đáng kể về mặt thống kê. Ở mức 5 yêu cầu mỗi giây, năm lỗi có nghĩa là toàn bộ hệ thống đã ngừng hoạt động. Hãy sử dụng các thư viện như Resilience4j hoặc Polly để thiết lập failureRateThreshold. Một tiêu chuẩn production phổ biến là ngắt mạch nếu 50% yêu cầu thất bại trong cửa sổ trượt 10 giây, với điều kiện có khối lượng tối thiểu ít nhất là 20 yêu cầu.

Sức mạnh của Fallbacks

Một bộ ngắt mạch hiệu quả nhất khi được kết hợp với chiến lược fallback. Khi một dependency bị hỏng, mã của bạn nên cung cấp một giải pháp thay thế “đủ tốt”. Đối với dịch vụ gợi ý, hãy trả về danh sách tĩnh các “Sản phẩm phổ biến”. Đối với dịch vụ hồ sơ người dùng, hãy trả về phiên bản dữ liệu đã được cache. Điều này đảm bảo trải nghiệm người dùng vẫn mượt mà ngay cả khi backend bị hỏng một phần.

Danh sách kiểm tra thực tế

Triển khai hiệu quả đòi hỏi nhiều hơn là chỉ bao bọc một hàm. Hãy ghi nhớ bốn quy tắc sau:

  • Ưu tiên Timeout nghiêm ngặt: Nếu thời gian phản hồi API trung bình của bạn là 100ms, hãy đặt timeout ở mức 300ms. Chờ đợi 10 giây cho một thất bại sẽ ngăn bộ ngắt mạch thực hiện công việc của nó một cách hiệu quả.
  • Trực quan hóa trạng thái: Đưa các chỉ số của bộ ngắt mạch vào Grafana. Bạn cần biết chính xác khi nào một mạch đang bị “flapping” (liên tục chuyển đổi giữa Open và Closed), vì điều này thường cho thấy mạng không ổn định hoặc ngưỡng thiết lập quá nhạy cảm.
  • Loại trừ lỗi phía Client: Không ngắt mạch đối với các lỗi HTTP 400, 401 hoặc 404. Đây là lỗi do người dùng hoặc dữ liệu sai, không phải do dịch vụ bị lỗi. Chỉ tính các lỗi 5xx và timeout mạng là thất bại.
  • Chaos Testing: Sử dụng một công cụ như Toxiproxy để mô phỏng độ trễ 500ms hoặc mất gói tin 20% trong môi trường staging. Hãy xác nhận rằng circuit breaker được kích hoạt và các fallback thực sự hoạt động trước khi sự cố thực tế xảy ra.

Việc áp dụng pattern này đòi hỏi một sự thay đổi trong tư duy. Bạn phải ngừng giả định rằng mạng luôn tin cậy. Bằng cách thiết kế cho các tình huống thất bại, bạn đảm bảo rằng một dependency chậm chạp duy nhất sẽ không làm sập toàn bộ cơ sở hạ tầng của mình.

Share: