Resiliency — CAP, Circuit Breaker & Chaos Engineering (BMS)
Mở đầu
Bài trước bàn về bảo mật — nhưng dù bảo vệ kỹ đến đâu, trong hệ thống phân tán thì lỗi vẫn là chuyện bình thường: server chết, network chập chờn, dependency trả response chậm. Chapter 12 của Building Microservices xoay quanh câu hỏi: làm sao để hệ thống không chỉ đứng vững mà còn phục hồi được khi sự cố xảy ra. Đây là chương quan trọng với backend engineer, vì nó phân biệt rõ hai khái niệm dễ nhầm: robustness (chịu được lỗi) và resiliency (phản ứng và hồi phục khi lỗi diễn ra). Hệ thống resilient không tránh khỏi lỗi — nó được thiết kế để sống sót qua lỗi.
Ảnh: panumas nikhomkhai — Pexels
CAP theorem — quyết định khi mạng lưới bị chia cắt
CAP là chữ viết tắt của Consistency (đồng nhất), Availability (sẵn sàng) và Partition Tolerance (chịu phân vùng). Định lý phát biểu rằng trong hệ phân tán, khi network bị chia cắt (partition), bạn chỉ có thể giữ được hai trong ba thuộc tính. Nhưng điểm quan trọng mà nhiều người hiểu sai: partition tolerance không phải thứ để chọn — trong môi trường phân tán, network partition là chuyện sẽ xảy ra, nên P gần như là điều kiện bắt buộc. Lựa chọn thực sự nằm ở C và A trong lúc partition xảy ra.
Nghĩa là khi mạng bị đứt, mỗi quyết định đều là đánh đổi: ưu tiên consistency thì node không thể xác nhận dữ liệu mới sẽ phải từ chối phục vụ (trả lỗi), ưu tiên availability thì node vẫn trả lời nhưng dữ liệu có thể cũ. Không có lựa chọn nào đúng tuyệt đối — nó phụ thuộc vào nghiệp vụ. Hệ thống thanh toán cần consistency, còn hệ thống hiển thị số lượt xem thì availability quan trọng hơn nhiều. Điều cốt lõi là phải biết rõ hệ thống của mình đang chọn gì, và chọn một cách có chủ đích thay vì để mặc định.
Circuit breaker — fail fast thay vì retry mù quáng
Khi một service phụ thuộc bị chậm hoặc sập, phản ứng tự nhiên của client là retry. Nhưng nếu dependency đã sập, retry chỉ làm tình hình tệ hơn: mỗi request đều chờ tới timeout, nuốt tài nguyên, và các client đồng loạt retry tạo thành retry storm khiến dependency càng ngập trong request — rồi lỗi lan rộng ra cả hệ thống (cascading failure).
Circuit breaker giải quyết bằng cơ chế giống cầu chì điện: theo dõi tỉ lệ lỗi khi gọi dependency. Khi lỗi vượt ngưỡng, mạch mở (open) — mọi request tới dependency bị từ chối ngay lập tức, không cần chờ timeout, không tốn tài nguyên. Sau một khoảng thời gian, mạch chuyển sang nửa mở (half-open), cho một lượng nhỏ request thử đi qua để kiểm tra dependency hồi phục chưa. Nếu thành công, mạch đóng lại (closed) và lưu lượng quay về bình thường; nếu vẫn lỗi, mạch mở tiếp. Nhờ đó hệ thống fail fast, và dependency đang quá tải có thời gian hồi phục thay vì bị bóp nghẹt bởi request liên tục.
Ảnh: Annas Zakaria — Pexels
Bulkhead & load shedding — cách ly để chặn lây lan
Bulkhead lấy ý tưởng từ tàu thuỷ: vỏ tàu chia nhiều khoang ngăn nước, một khoang thủng thì nước chỉ tràn vào khoang đó, tàu vẫn nổi. Áp vào microservices, mỗi dependency nên có pool tài nguyên riêng (connection pool, thread pool) thay vì dùng chung một pool. Nếu không, một dependency chậm có thể nuốt hết thread pool của service, khiến service không phục vụ được cả những request không liên quan tới dependency đó — một điểm chậm kéo sập cả service.
Load shedding là chủ động cắt bớt tải khi hệ thống có dấu hiệu quá tải, thay vì cố phục vụ đến cùng. Khi latency tăng hoặc queue đầy, service có thể trả về 503 hoặc drop request, hoặc tắt dần các tính năng không quan trọng (graceful degradation) để giữ phần lõi hoạt động. Nguyên tắc là phát hiện sớm và hành động trước khi quá tải kéo sập toàn bộ — theo dõi metrics và đặt ngưỡng cảnh báo trước khi mọi thứ vượt khỏi tầm kiểm soát.
Chaos engineering — tiêm lỗi có chủ đích
Cách duy nhất để biết hệ thống có resilient hay không là... để nó gặp lỗi. Chaos engineering làm điều đó một cách có kiểm soát: chủ động tiêm lỗi vào hệ thống để kiểm chứng các giả định. Netflix là tiên phong với Chaos Monkey — công cụ tự động tắt ngẫu nhiên instance trong production, buộc đội ngũ đảm bảo hệ thống vẫn hoạt động khi mất node. Nếu hệ thống sụp vì một instance chết, đó không phải lỗi của Chaos Monkey, mà là cơ hội để sửa trước khi sự cố thật xảy ra.
Chaos engineering không có nghĩa là phá hệ thống bừa bãi. Nó bắt đầu từ việc lập giả thuyết ("hệ thống vẫn ổn nếu mất một database replica"), thiết kế thí nghiệm nhỏ trong phạm vi an toàn, đo lường tác động, rồi dần mở rộng. Game Day — dựng kịch bản sự cố để cả đội cùng tập ứng phó — cũng là một hình thức phổ biến. Mục tiêu cuối cùng không phải là tạo lỗi, mà là xây dựng niềm tin dựa trên bằng chứng thay vì giả định.
Ảnh: Tara Winstead — Pexels
Key Takeaways
- Robustness là chịu được lỗi, còn resiliency là phản ứng và phục hồi — hệ thống phân tán nên được thiết kế theo hướng sau.
- CAP thường bị hiểu sai: partition tolerance là bắt buộc, lựa chọn thực sự là consistency hay availability trong lúc partition.
- Circuit breaker giúp fail fast và ngăn cascading failure — luôn kèm timeout hợp lý, retry có backoff và jitter.
- Bulkhead cách ly tài nguyên của từng dependency, ngăn một điểm chậm nuốt hết tài nguyên của service.
- Load shedding chủ động cắt tải khi quá tải thay vì cố phục vụ đến sập.
- Chaos engineering kiểm chứng giả định về độ bền bằng cách tiêm lỗi có kiểm soát.
Glossary
| Thuật ngữ | Ý nghĩa |
|---|---|
| Resiliency | Khả năng phản ứng và phục hồi khi lỗi xảy ra |
| Robustness | Khả năng chịu đựng lỗi mà không bị ảnh hưởng |
| CAP theorem | Định lý về Consistency, Availability, Partition Tolerance trong hệ phân tán |
| Partition | Sự chia cắt mạng khiến các node mất kết nối với nhau |
| Circuit breaker | Cơ chế ngắt mạch khi dependency lỗi liên tục, fail fast thay vì chờ timeout |
| Cascading failure | Lỗi lan truyền: một service sập kéo theo các service phụ thuộc sập theo |
| Bulkhead | Cách ly tài nguyên (thread/connection pool) riêng cho từng dependency |
| Load shedding | Chủ động cắt tải khi hệ thống quá tải để giữ phần lõi hoạt động |
| Backoff & jitter | Chiến lược retry: tăng dần khoảng chờ và thêm ngẫu nhiên để tránh retry storm |
| Chaos engineering | Chủ động tiêm lỗi có kiểm soát để kiểm chứng độ bền hệ thống |
| Fail fast | Thất bại nhanh: từ chối ngay lập tức thay vì chờ timeout lâu |
Kết
Resiliency không phải là một tính năng thêm vào cuối, mà là một tư duy thiết kế xuyên suốt: biết lỗi sẽ xảy ra, quyết định rõ đánh đổi (CAP), dùng circuit breaker và bulkhead để chặn lây lan, chủ động cắt tải khi quá tải, và kiểm chứng mọi giả định bằng chaos engineering. Làm được những điều này, hệ thống không còn là thứ mong manh phụ thuộc vào may mắn. Chapter tiếp theo của series sẽ bàn về scaling — cách mở rộng hệ thống khi lưu lượng tăng, một chủ đề liên hệ mật thiết với resiliency, vì tải cao chính là lúc lỗi dễ lộ diện nhất.