Designing Resilient Systems Beyond Retries (Part 2): Bulkheading, Load Balancing, and Fallbacks

Bài tóm tắt Grab 2019 3. Quy mô lớn Độ khó: Dễ

Grab · 2019 · 3. Quy mô lớn · Độ khó: Dễ — bài gốc

Bối cảnh

Bài thứ hai trong series ba phần của Grab về các kỹ thuật chịu lỗi "ngoài retry" (bài 1 nói về rate limiting). Bài thiên về giải thích pattern, chỉ có vài đoạn ngắn về cách Grab áp dụng.

Thay đổi chính (các pattern và cách Grab dùng)

  • Bulkhead (cô lập): chia thành các khoang để lỗi ở một chỗ không lan ra cả hệ thống. Có nhiều mức:

    • phần cứng (region, AZ);
    • OS (VM, container);
    • process (tách service);
    • thấp nhất là connection pool và thread pool, giới hạn tài nguyên một thao tác được dùng.

    Thư viện Hystrix cho Go có MaxConcurrentRequests gắn theo tên circuit. Lời khuyên của Grab: đặt mỗi thao tác/API một tên circuit, để một endpoint lỗi không chiếm hết phần của các endpoint còn khoẻ.

  • Load balancing, bốn kiểu:

    • proxy (như ELB): tiện, nhưng thêm một hop;
    • client-side: nhanh hơn, nhưng code phức tạp và bug thì nguy hiểm;
    • lookaside: logic nằm ở một service riêng, client vẫn nối thẳng backend, nhưng service đó thành thêm một điểm hỏng;
    • service mesh + sidecar.

    Grab tự xây CSDP (client-side, dựa trên etcd).

  • Fallback khi retry vô ích, bốn kiểu:

    • fail silently: trả rỗng cho dữ liệu không thiết yếu (badge thông báo hiện 0);
    • tính tại chỗ: giá trị mặc định hoặc công thức đơn giản;
    • giá trị cache: dùng hai thời hạn hết hạn, một cho lúc bình thường và một dài hơn cho lúc service lỗi;
    • backup service: phải đơn giản hơn hẳn service chính và không phụ thuộc gì vào hệ thống gốc.

    Grab-Kit có sẵn fallback trả về cache khi service phía dưới không phản hồi (Grab 2018). Grab còn dựng một backup service sao chép chức năng lõi.

Vì sao

Theo Grab: retry không cứu được khi service phía dưới hỏng hẳn; cần giới hạn thiệt hại (bulkhead) và giảm chất lượng một cách có kiểm soát (fallback) thay vì lỗi toàn phần.

Bài học cho SWE

  • Pool kết nối/luồng là bulkhead rẻ nhất, không chỉ là tối ưu hiệu năng.
  • Gắn giới hạn đồng thời theo từng thao tác, không theo cả service.
  • Backup service chỉ đáng làm nếu đơn giản hơn và độc lập hẳn với hệ thống chính.
  • Đối chiếu: Discord dùng semaphore và cố ý không dùng circuit breaker (Discord 2017); Grab dùng MaxConcurrentRequests của Hystrix, tức là một semaphore gắn theo circuit. Hai cách gần nhau hơn tưởng.

Liên quan