0

🏗️🧠 Circuit Breaker & Retry: đừng để một service chết kéo cả hệ thống - System Design P16

Retry, Timeout & Circuit Breaker: Nghệ Thuật Kiểm Soát "Hố Đen" Lỗi Trong Hệ Thống Phân Tán

1. Lời mở đầu: Tiếng vang từ "chiến trường" Production

Hãy tưởng tượng một kịch bản mà mọi kỹ sư Site Reliability Engineering (SRE) đều khiếp sợ: 14:00 chiều thứ Sáu, hệ thống đang xử lý 50.000 Request Per Second (RPS) một cách ổn định. Đột nhiên, màn hình giám sát im bặt một cách kỳ lạ – không phải vì không có traffic, mà vì độ trễ (latency) tăng vọt đến mức các biểu đồ không kịp vẽ.

Dashboard bắt đầu nhuộm đỏ. Các dịch vụ tiền phương (upstream) báo lỗi cạn kiệt Thread Pool. Log hệ thống tràn ngập những dòng "Connection Timeout". Trong cơn hoảng loạn, phản xạ đầu tiên của đội trực chiến thường là tăng thêm tài nguyên hoặc khởi động lại các cụm Service. Nhưng đây chính là lúc sai lầm chết người xuất hiện: Càng khởi động lại, các node vừa mới "ngoi lên" ngay lập tức bị nhấn chìm bởi một cơn sóng dữ liệu khổng lồ từ các yêu cầu thử lại (Retry Storm).

Đây không đơn thuần là lỗi code; đây là hiện tượng Cascading Failure (lỗi lan truyền). Nó bắt đầu từ một "hố đen" nhỏ – một Service hạ nguồn (downstream) phản hồi chậm – và nhanh chóng trở thành một đợt tự tấn công từ chối dịch vụ (Self-inflicted DDoS).

Là một Architect, tôi đã chứng kiến những hệ thống triệu đô sụp đổ chỉ vì thiếu đi sự "buông bỏ" đúng lúc. Trong thế giới phân tán, câu hỏi không bao giờ là "Làm sao để không có lỗi?", mà là "Khi lỗi xảy ra, làm sao để nó không kéo sập toàn bộ platform?". Timeout, Retry và Circuit Breaker chính là bộ ba vắc-xin giúp chúng ta cô lập vùng ảnh hưởng (Blast Radius), biến một thảm họa tiềm tàng thành một sự cố có thể kiểm soát.


2. Phá vỡ những "niềm tin" sai lầm (Common Misconceptions)

Trên hành trình từ một Junior lên Senior, rào cản lớn nhất không phải là kiến thức mới, mà là những định kiến cũ. Trong bối cảnh hệ thống chịu tải cao, những gì ta coi là "an toàn" thường chính là thứ giết chết hệ thống nhanh nhất.

Niềm tin phổ biến (Common Belief) Thực tế từ "chiến trường" (Production Reality)
Retry càng nhiều càng tốt để đảm bảo thành công. Retry vô tội vạ là "vũ khí tự sát". Nó biến một lỗi tạm thời thành một cơn bão traffic đánh sập downstream đang hấp hối.
Timeout nên để dài để chờ kết quả chính xác. Timeout quá dài là "kẻ trộm" tài nguyên. Nó giữ chân Thread, Memory và Connection, làm nghẽn toàn bộ luồng xử lý phía trên.
Circuit Breaker chỉ dành cho hệ thống khổng lồ. Đây là cơ chế sinh tồn cơ bản. Bất kỳ Network Call nào cũng cần một "cầu chì" để bảo vệ tính toàn vẹn của hệ thống.
Lỗi của Service hạ nguồn là lỗi cục bộ. Trong Microservices, không có lỗi nào là cục bộ. Nếu không có rào chắn, một service nhỏ ở cuối chuỗi sẽ kéo sập cả Platform.

Góc nhìn Senior: Đừng nhìn các Pattern này như những tính năng thêm thắt. Hãy nhìn chúng như những quyết định đánh đổi (trade-offs). Resilience không miễn phí; nó đánh đổi độ phức tạp và tài nguyên để lấy sự ổn định của hệ thống.


3. Timeout: Ranh giới của sự kiên nhẫn và hiệu suất

Trong thiết kế hệ thống, triết lý "biết buông bỏ" quan trọng hơn "cố gắng bám trụ". Một request không bao giờ nên được phép treo vô hạn. Việc thiết lập Timeout không phải là biểu hiện của sự thiếu kiên nhẫn, mà là cơ chế bảo vệ tài nguyên quý giá nhất của server: Threads.

Tại sao cần Timeout?

Khi Service A gọi Service B và Service B bị treo, Service A phải dành ra một Thread để chờ đợi. Nếu hàng ngàn request cùng đổ vào, toàn bộ Thread Pool của Service A sẽ bị chiếm dụng bởi các "zombie requests". Lúc này, Service A không còn khả năng xử lý bất kỳ việc gì khác, dù là những task nhẹ nhàng nhất. Hệ thống rơi vào trạng thái "Deadly Embrace" (cái ôm chết chóc).

Sự tiến hóa của tư duy Timeout

  • Stage 1: Hardcoded Timeouts. Thời kỳ Monolith, chúng ta thường để mặc định 30s hoặc 60s cho mọi DB call hay API call.
  • Stage 2: Configurable Timeouts. Khi sang Distributed System, chúng ta bắt đầu nhận ra mỗi service cần một ngưỡng riêng dựa trên P99 latency.
  • Stage 3: Timeout Budgets & Deadline Propagation. Đây là tầng nhận thức của các kiến trúc sư thực thụ.

Timeout Budget và Deadline Propagation

Hãy tưởng tượng chuỗi request: User -> Gateway -> Service A -> Service B -> Service C.Nếu User chỉ có thể đợi tối đa 2 giây, và Gateway đã tốn 500ms để xử lý, thì ngân sách còn lại cho toàn bộ chuỗi phía sau chỉ là 1.5 giây.

Nếu Service A không biết điều này và vẫn thiết lập Timeout tĩnh là 2 giây cho việc gọi Service B, nó có thể đang thực hiện một công việc vô nghĩa. Vì khi Service B trả về kết quả thành công sau 1.8 giây, thì ở tầng Gateway, request của User đã bị ngắt từ lâu.

Giải pháp Senior: Sử dụng Deadline Propagation. Giá trị timeout_remaining sẽ được gửi qua HTTP Header (như X-Request-Deadline). Mỗi service hạ nguồn sẽ lấy giá trị này, trừ đi thời gian xử lý của chính nó, rồi mới truyền tiếp cho service sau. Điều này đảm bảo toàn bộ hệ thống luôn đồng bộ về "ngân sách thời gian".

Góc nhìn Senior: Một Timeout quá ngắn sẽ làm tăng tỷ lệ False Failures (lỗi giả), khiến hệ thống bất ổn dù tải không cao. Ngược lại, Timeout quá dài sẽ làm giảm Throughput (băng thông xử lý). Việc tìm ra "điểm ngọt" (sweet spot) thường dựa trên dữ liệu P99.9 latency cộng thêm một khoảng đệm (buffer) an toàn.


4. Retry: Con dao hai lưỡi và chiến thuật "Lùi bước" (Backoff)

Retry là phản xạ tự nhiên của chúng ta khi gặp lỗi mạng tạm thời (Transient Failure). Nhưng trong môi trường phân tán, Retry chính là nguyên liệu của những vụ nổ dây chuyền.

Bài toán "Retry Storm"

Giả sử Service Downstream đang gặp sự cố và chỉ có thể xử lý 50% traffic. Nếu 1000 client đồng loạt Retry 3 lần ngay khi gặp lỗi, lưu lượng truy cập vào Downstream sẽ tăng từ 1000 lên thành 1000+(500×3)=2500 request.Sự gia tăng đột ngột 250% traffic này sẽ ngay lập tức bóp nghẹt chút hy vọng hồi phục cuối cùng của Downstream. Đây là kịch bản "tự sát có kế hoạch".

Chiến thuật Exponential Backoff & Jitter

Để Retry an toàn, chúng ta cần hai yếu tố:

  1. Exponential Backoff: Thay vì thử lại ngay lập tức (1s, 1s, 1s), chúng ta tăng dần thời gian chờ (1s, 2s, 4s, 8s...). Điều này tạo ra khoảng trống cho Downstream tự chữa lành hoặc thực hiện scale-out.
  2. Jitter (Độ nhiễu ngẫu nhiên): Đây là kỹ thuật tối quan trọng để phá vỡ sự đồng bộ. Nếu 1000 node cùng đợi đúng 2 giây rồi mới Retry, chúng sẽ tạo ra những đỉnh traffic (spikes) khổng lồ tại cùng một thời điểm – hiện tượng Thundering Herd.

Có hai loại Jitter phổ biến:

  • Equal Jitter:wait = base_delay / 2 + random(0, base_delay / 2)
  • Full Jitter:wait = random(0, base_delay)

Lời khuyên từ thực tế: Luôn chọn Full Jitter. Nó giúp phân tán traffic Retry đều nhất trên trục thời gian, biến những "ngọn núi" traffic thành những "cánh đồng" phẳng, giúp hệ thống hạ nguồn dễ thở hơn nhiều.

Khi nào nên Retry? (Retry Conditions)

Đừng bao giờ Retry mù quáng. Một Senior sẽ phân loại lỗi:

  • Retry: 503 Service Unavailable, 504 Gateway Timeout, Connect Timeout, hoặc 429 Too Many Requests (nếu có header Retry-After).
  • Tuyệt đối KHÔNG Retry: 400 Bad Request, 401 Unauthorized, 404 Not Found. Những lỗi logic này sẽ không bao giờ biến mất dù bạn có thử lại một triệu lần.

Góc nhìn Senior: Mỗi lần Retry là một lần tiêu tốn tài nguyên. Hãy thiết lập một Max Retry Attempts (thường là 2 hoặc 3) và luôn đi kèm với Retry Budget (ví dụ: chỉ cho phép Retry nếu tỷ lệ lỗi hiện tại của service thấp hơn 10%). Nếu tỷ lệ lỗi quá cao, hãy dừng Retry để bảo vệ chính mình.


5. Circuit Breaker: "Cầu chì" sinh tồn của hệ thống

Nếu Timeout là sự kiên nhẫn, Retry là sự kiên trì, thì Circuit Breaker (CB) là sự tỉnh táo. Khi một hệ thống đã "ngã", đừng cố gắng đẩy thêm áp lực vào đó.

Mô hình State Machine và Logic Toán học

CB hoạt động như một máy trạng thái (State Machine) bảo vệ kết nối:

  1. Closed (Bình thường): Request đi qua. CB đếm số lỗi trong một Sliding Window (Cửa sổ trượt).
  2. Open (Ngắt mạch): Khi tỷ lệ lỗi vượt ngưỡng (Threshold), ví dụ > 50% trong 10 giây qua, CB sẽ "ngắt". Mọi request tiếp theo sẽ bị từ chối ngay lập tức ở phía client (Fail Fast).
  3. Half-Open (Thử nghiệm): Sau một khoảng thời gian (Sleep Window), CB cho phép một vài request "thăm dò" đi qua. Nếu chúng thành công, nó quay lại Closed. Nếu vẫn lỗi, nó tái mở mạch Open.

Cách chọn Threshold:

  • Count-based: Ngắt sau N lỗi liên tiếp. Phù hợp cho traffic thấp.
  • Time-based (Sliding Window): Ngắt nếu tỷ lệ lỗi trong X giây vượt mức. Phù hợp cho traffic cao vì nó phản ánh chính xác tình trạng sức khỏe của hệ thống hơn là những lỗi lẻ tẻ.

Tư duy Fallback: Kế hoạch B cho trải nghiệm người dùng

Ngắt mạch không có nghĩa là hiển thị màn hình trắng cho người dùng. Đó là lúc Graceful Degradation (Suy giảm chức năng có kiểm soát) lên tiếng:

  • Nếu Service "Gợi ý mua sắm" chết: Fallback bằng cách lấy dữ liệu từ cache cũ hoặc danh sách "Bán chạy nhất" mặc định.
  • Nếu Service "Tính phí vận chuyển" chậm: Fallback bằng một mức phí cố định trung bình thay vì treo giỏ hàng của khách.

Cái giá của Circuit Breaker: Duy trì trạng thái cho hàng ngàn CB trong bộ nhớ không hề rẻ. Mỗi CB cần một cấu trúc dữ liệu để theo dõi cửa sổ thời gian và thống kê lỗi. Ở quy mô lớn, việc này có thể tiêu tốn hàng trăm MB RAM và gây áp lực lên Garbage Collector (GC).

Góc nhìn Senior: Cẩn thận với False Positives. Nếu bạn đặt ngưỡng CB quá nhạy (ví dụ 5% lỗi), một đợt "nấc cụt" nhẹ của mạng nội bộ cũng có thể khiến toàn bộ hệ thống ngắt mạch, gây ra một đợt Downtime tự thân.


6. Góc nhìn SRE: Điều tiết áp lực và Quan sát thực tế

Từ lăng kính của một Site Reliability Engineer, Resilience không chỉ là code, mà là quản lý dòng chảy áp lực (Traffic Management).

Failure Isolation và Blast Radius

Mục tiêu tối thượng của chúng ta là giới hạn Blast Radius (vùng ảnh hưởng). Khi Service thanh toán gặp lỗi, nó không được phép làm tê liệt tính năng tìm kiếm sản phẩm. Bộ ba Timeout - Retry - CB tạo ra những vách ngăn (Bulkheads) ngăn chặn hiện tượng domino. Chúng ta chấp nhận mất một phần tính năng để bảo vệ sự sống còn của toàn bộ Platform.

Kết nối với Observability (Episode 15)

Bạn không thể thiết lập CB Threshold hay Timeout dựa trên cảm giác.

  • Sử dụng Metrics để theo dõi trạng thái của Circuit Breaker. Một CB chuyển sang trạng thái Open phải được kích hoạt Alert ngay lập tức vì đó là dấu hiệu hệ thống đang "gồng mình" chống chọi.
  • Sử dụng Tracing để tìm ra nút thắt cổ chai thực sự. Có thể Service A chậm không phải do chính nó, mà do nó đang chờ một DB Lock hoặc một Downstream thứ ba mà bạn không ngờ tới.

Góc nhìn Senior: Resilience là một sự đánh đổi về kinh tế. Việc xây dựng một hệ thống chịu được mọi loại lỗi có thể tốn gấp 10 lần chi phí so với một hệ thống chỉ chịu được 99% lỗi. Hãy đặt ngưỡng bảo vệ dựa trên yêu cầu của Business, không phải dựa trên sự cầu toàn thái quá của kỹ thuật.


7. Tổng kết và Bài học kinh nghiệm (Key Takeaways)

Để thực sự làm chủ nghệ thuật kiểm soát lỗi, hãy khắc cốt ghi tâm những bài học sau:

  1. Hệ thống phân tán luôn lỗi: Đừng cố xây dựng một pháo đài không thể phá vỡ. Hãy xây dựng một hệ thống có khả năng tự phục hồi (Self-healing).
  2. Timeout là rào chắn đầu tiên: Mọi Network Call đều phải có Timeout. Sử dụng Deadline Propagation để tối ưu hóa ngân sách thời gian.
  3. Retry là con dao hai lưỡi: Luôn dùng Exponential Backoff kết hợp với Full Jitter. Đừng bao giờ Retry mà không có chiến lược "lùi bước".
  4. Circuit Breaker là sinh tồn: Sử dụng CB để ngăn chặn Cascading Failure và cho phép Downstream có thời gian hồi phục.
  5. Fallback là văn hóa: Thiết kế hệ thống sao cho khi một phần bị hỏng, người dùng vẫn nhận được một giá trị nào đó thay vì một thông báo lỗi vô cảm.
  6. Quyết định dựa trên dữ liệu: Sử dụng P99 Latency và Metrics từ hệ thống giám sát để tinh chỉnh các ngưỡng (Thresholds).


🚀 Tiếp tục hành trình cùng TechCraft

Bài viết này là một phần trong hành trình khám phá Backend Engineering, System Design và Production Systems tại TechCraft.

Nếu bạn muốn học theo một lộ trình rõ ràng hơn, TechCraft đang xây dựng Dev Insider như nơi tập trung các series chuyên sâu hơn về:

  • Backend Internals
  • Database Internals
  • Transaction & Consistency
  • Distributed Systems
  • Production System Design
  • AI-Proof Engineer

🚀 Dev Insider
https://www.patreon.com/techcraft_official/posts/vi-sao-dev-ra-161163881?collection=2220113

📘 Facebook
https://www.facebook.com/techcraft.official

🎥 YouTube
https://www.youtube.com/@techcraft.official

🎵 TikTok
https://www.tiktok.com/@techcraft.official

Hiểu hệ thống. Không chỉ framework.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí