Đây là cascading failure, và nguyên nhân gốc thường là thiếu timeout, không phải service kia chết.
Chuỗi diễn biến: service B trả lời sau 30s thay vì 50ms. Mỗi request tới A giữ một thread/connection chờ B. Thread pool và connection pool của A cạn. A không còn tài nguyên xử lý cả những endpoint không liên quan đến B. Client của A timeout rồi retry, nhân đôi tải. A ngừng phục vụ, kéo theo C đang gọi A. Một dịch vụ phụ làm sập luồng chính.
Các lớp phòng thủ, theo thứ tự quan trọng:
1. Timeout ở mọi lời gọi ra ngoài. Không có timeout mặc định "vô hạn". Chọn theo p99 thực đo (ví dụ p99 = 200ms thì đặt 500ms-1s), không đặt bừa 30s.
2. Timeout budget. Nếu client cho A 2s, A không được đặt timeout gọi B là 3s. Ngân sách phải giảm dần theo chuỗi; hết ngân sách thì huỷ luôn, đừng làm việc mà kết quả chắc chắn bị bỏ.
3. Giới hạn retry + backoff có jitter. Retry mù làm tải tăng bội trong lúc hệ thống đang yếu. Chỉ retry lỗi tạm thời và thao tác idempotent, giới hạn số lần, và dùng token bucket cho retry để tổng retry không vượt quá vài phần trăm lưu lượng.
4. Circuit breaker. Khi tỉ lệ lỗi/chậm vượt ngưỡng, mở mạch và fail nhanh thay vì để request xếp hàng; sau một khoảng chuyển sang half-open thử vài request để dò hồi phục.
5. Bulkhead. Tách pool tài nguyên theo phụ thuộc: lời gọi tới B dùng pool riêng, cạn thì chỉ tính năng liên quan B hỏng, phần còn lại vẫn chạy.
6. Graceful degradation. Xác định trước phần nào có thể bỏ (gợi ý sản phẩm, đánh giá) để giữ luồng chính (đặt hàng, thanh toán).
Lưu ý về fallback: đường fallback ít được chạy nên hay hỏng đúng lúc cần, và có thể tự tạo tải mới. Ưu tiên fail nhanh + giảm chức năng hơn là fallback phức tạp.