0

Đừng để hệ thống của bạn tự "Dẫm đạp" lên nhau: Chuyện về Exponential Backoff

Chào anh em, lại là tôi đây.

Trong đời làm nghề "thợ xây" hệ thống, có những bài học mà chúng ta không học được từ sách vở hay các khóa học Udemy 10$, mà chỉ học được khi... hệ thống sập ngay giữa đêm giao thừa hoặc đúng lúc Flash Sale đang đạt đỉnh.

1. Câu chuyện từ một "vụ nổ" không báo trước

Cách đây vài năm, tôi tham gia vận hành một hệ thống thanh toán. Mọi thứ đang chạy êm ru thì bỗng dưng Service đối tác (một cổng thanh toán lớn) bị chập chờn.

Theo thói quen "ngây thơ" của một dev mới vào nghề, team tôi đã cài đặt cơ chế Retry (thử lại): "Nếu gọi API không được, hãy thử lại ngay lập tức!".

Và chuyện gì đến cũng đến. Khi cổng thanh toán vừa chớm hồi phục, 10.000 request đang xếp hàng đồng loạt "tấn công" ngược lại họ. Phía đối tác chưa kịp thở đã lại sập tiếp. Hệ thống của chúng tôi thì treo cứng vì tốn tài nguyên để duy trì hàng vạn kết nối đang retry điên cuồng.

Đó là lúc tôi nhận ra: Retry không đúng cách còn độc hại hơn cả việc không Retry.

2. Exponential Backoff là gì? (Giải thích kiểu "anh em mình")

Hãy tưởng tượng bạn đang gọi điện cho một cô gái nhưng cô ấy không nghe máy.

  • Retry ngây thơ: Bạn gọi lại ngay lập tức, liên tục 10 cuộc. Kết quả: Bạn bị chặn số vì tội quấy rối.
  • Exponential Backoff: Bạn gọi lần 1 không được -> Đợi 1 phút mới gọi lại. Lần 2 vẫn không được -> Đợi 2 phút. Lần 3 -> Đợi 4 phút... rồi 8 phút, 16 phút.

Nói một cách kỹ thuật, đây là chiến lược giãn cách thời gian chờ giữa các lần thử lại theo hàm mũ. Thay vì cứ mỗi 1 giây thử lại một lần, chúng ta sẽ nhân đôi (hoặc nhân theo một hệ số) thời gian chờ sau mỗi lần thất bại.

Công thức cơ bản thường là:

wait_time=base×2attemptwait\_time = base \times 2^{attempt}

3. Tại sao "Gừng càng già" thì càng dùng Exponential Backoff?

Trong các hệ thống lớn (High Scale), việc giãn cách này mang lại 3 giá trị sống còn:

  1. Cho hệ thống phía sau (Upstream) thời gian để "thở": Khi một Service bị quá tải, điều cuối cùng nó muốn là một "cơn bão" request thử lại đổ ập vào. Backoff giúp áp lực giảm dần theo thời gian.

  2. Tiết kiệm tài nguyên cho chính mình: Việc loop liên tục để retry sẽ đốt cháy CPU và Network của chính server bạn.

  3. Tránh hiệu ứng "Thundering Herd" (Đàn gia súc hoảng loạn): Khi hàng ngàn client cùng retry một lúc, chúng sẽ tạo ra những đỉnh nhọn (spike) về lưu lượng, làm chết bất cứ hệ thống nào vừa mới khởi động lại.

4. Bí kíp "Gia vị" không thể thiếu: Jitter

Nếu anh em chỉ dùng Exponential Backoff thuần túy, anh em vẫn có thể dính đạn. Tại sao? Vì nếu 1.000 request cùng lỗi tại thời điểm tt, thì sau đúng 2n2^n giây, cả 1.000 request đó lại cùng ập vào một lúc.Để giải quyết, những "lão làng" thường thêm vào một chút Jitter (nhiễu ngẫu nhiên).Ví dụ: Thay vì đợi đúng 2 giây, hệ thống sẽ đợi một khoảng ngẫu nhiên từ 1.5 đến 2.5 giây. Việc "rải" các request ra theo thời gian sẽ giúp đồ thị lưu lượng trở nên bằng phẳng và dễ chịu hơn rất nhiều.

5. Áp dụng sao cho chuẩn?

  • Luôn có Max Retries: Đừng thử lại mãi mãi. Sau khoảng 5-7 lần, hãy trả về lỗi cho User hoặc đưa vào Dead Letter Queue.
  • Luôn có Max Backoff Time: Đừng để User phải chờ đến... 2 ngày mới thấy request retry. Giới hạn thời gian chờ tối đa (ví dụ: tối đa 30 giây).
  • Kết hợp với Circuit Breaker: Khi tỉ lệ lỗi quá cao, hãy "ngắt mạch" luôn, đừng tốn công retry vô ích.

Lời kết

Exponential Backoff không chỉ là một thuật toán, nó là tư duy về sự Thấu cảm (Empathy) trong kiến trúc hệ thống: Hãy đối xử tử tế với các Service khác khi chúng đang gặp khó khăn, và hệ thống của bạn sẽ bền bỉ hơn gấp bội.

Anh em đã bao giờ gặp cảnh hệ thống "tự sát" vì retry quá đà chưa? Hãy chia sẻ câu chuyện đau thương (hoặc thành công) của mình ở dưới comment nhé!


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í