Họ đang đo gì
Bạn có nghĩ tới hiệu ứng cộng dồn của nhiều client không, hay chỉ nghĩ tới một client.
Trả lời ngắn~30 giây
Backoff mũ để không dồn dập, JITTER ngẫu nhiên để hàng nghìn client không cùng thử lại một lúc, và giới hạn cứng để không retry mãi. Jitter là phần hay bị bỏ và cũng là phần quan trọng nhất: không có nó, mọi client thất bại cùng lúc sẽ retry cùng lúc, tạo ra những đợt sóng đồng bộ đánh vào dịch vụ đang cố hồi phục — đúng lúc nó cần ít tải nhất.
Dịch vụ phụ thuộc bắt đầu lỗi. Cả 400 client cùng nhận lỗi tại một thời điểm — đó là điều kiện ban đầu quan trọng nhất.
Giải thích sâu
Chỉ nên retry những lỗi CÓ THỂ tạm thời: timeout, lỗi kết nối, 429, 503. Retry một 400 hay 422 là vô nghĩa vì yêu cầu vẫn sai, và nó chỉ đốt hạn mức của bạn. Với 5xx thì cẩn thận hơn: nếu thao tác không idempotent, retry có thể tạo bản ghi trùng — server có thể đã xử lý xong rồi mới lỗi lúc trả về. Đó là lý do retry và idempotency key luôn đi cùng nhau.
Cái bẫy cấu trúc là retry lồng nhau. Nếu tầng A retry 3 lần, tầng B bên trong cũng retry 3 lần và tầng C nữa, thì một yêu cầu người dùng sinh ra 27 lời gọi tới dịch vụ dưới cùng. Đây là cách rất phổ biến để tự tạo ra một cuộc tấn công vào chính mình. Quy tắc thực dụng: chỉ retry ở MỘT tầng, thường là tầng gần nhất với lỗi, và các tầng trên chỉ truyền lỗi lên.
Circuit breaker là lớp bổ sung cần thiết khi lỗi kéo dài. Sau N lần thất bại liên tiếp, mạch mở và mọi lời gọi tiếp theo thất bại NGAY mà không đi ra ngoài — bạn ngừng lãng phí thời gian chờ timeout và ngừng đẩy tải lên dịch vụ đang chết. Sau một khoảng, mạch chuyển sang trạng thái nửa mở cho một vài lời gọi thăm dò đi qua. Ý tưởng cốt lõi: khi biết chắc sẽ hỏng, thất bại nhanh tốt hơn thất bại chậm.
// Full jitter (khuyến nghị của AWS): chọn ngẫu nhiên trong [0, backoff]
async function retry<T>(fn: () => Promise<T>, max = 4): Promise<T> {
for (let attempt = 0; ; attempt++) {
try { return await fn(); }
catch (err) {
if (attempt >= max || !isTransient(err)) throw err;
const cap = Math.min(30_000, 200 * 2 ** attempt);
await sleep(Math.random() * cap); // <- jitter, không phải cap cố định
}
}
}Câu hỏi tiếp theo họ sẽ hỏi
?Timeout đặt bao nhiêu?
Bắt đầu từ p99 của dịch vụ đó cộng một biên, chứ không phải một con số tròn tuỳ ý. Và tổng thời gian của mọi lần retry phải nhỏ hơn timeout của tầng gọi bạn — nếu không, tầng trên bỏ cuộc trong khi bạn còn đang thử lại, và công đó thành lãng phí hoàn toàn.
?Retry budget là gì?
Giới hạn TỔNG tỷ lệ retry trên toàn client, ví dụ retry không vượt quá 10% lưu lượng thường. Nó chặn kịch bản mọi request đều đang ở lần thử thứ ba, thứ mà giới hạn theo từng request không chặn được.
Trả lời thế này là mất điểm
- Retry ngay lập tức không có backoff. Đó là cách nhanh nhất để biến một trục trặc thành sự cố.
- Backoff mũ mà không có jitter. Bạn vẫn có sóng đồng bộ, chỉ là thưa hơn.