Backend

System Design — Bài 21: Thiết kế Flash Sale: chịu tải đột biến và không oversell

SSite Admin
10 tháng 10, 2026 8 phút đọc 0 lượt xem

Một triệu lượt mua trong 10 giây nhưng chỉ có 1.000 sản phẩm: phần lớn request không thể thành công. Hệ thống phải trả kết quả sớm với chi phí thấp, trong khi đường xác nhận vẫn giữ đúng tồn kho dưới retry, failover và race giữa thanh toán với hết hạn.

Khóa học System Design: mục lục 21 bài — bạn đang đọc bài 21.

Các con số dưới đây là giả định phục vụ học thiết kế, không phải số liệu hay kiến trúc nội bộ của sản phẩm được nhắc tên. Dung lượng dùng đơn vị thập phân; capacity thực tế phải đo theo workload và mục tiêu chất lượng.

Đề bài và con số

Giả sử 1 triệu người, mỗi người gửi một request mua trong mười giây. Trung bình trong đợt là 100.000 request mỗi giây; đỉnh tức thời có thể cao hơn nhiều. Chưa tính xem trang, bot và retry.

Chỉ có 1.000 sản phẩm nên tối đa một phần nghìn lượt mua ban đầu thành công. Ít nhất 99,9 phần trăm không thể nhận hàng trong giả định mỗi lượt mua một sản phẩm. Nếu mọi request đều xuống database kiểm tra và tranh khóa, phần thất bại sẽ chiếm phần lớn tài nguyên.

Mục tiêu là giữ đường xác nhận dưới capacity đã đo, trả kết quả rõ và tránh tạo cảm giác đã mua khi mới được xếp hàng. Công bằng cũng phải định nghĩa: theo thứ tự vé, giới hạn mỗi tài khoản hay cách phân bổ khác. Tính atomic của kho không tự bảo đảm công bằng.

Phễu giảm tải theo tầng

Sơ đồ: Phễu giảm tải theo tầng

Xem sơ đồ đầy đủ: Phễu giảm tải theo tầng

CDN phục vụ trang tĩnh và ảnh, tránh origin xử lý phần có thể cache. Cờ hết hàng có thể giúp trả sớm, nhưng phải có chính sách cập nhật và chỉ là dữ liệu hỗ trợ; xác nhận cuối vẫn theo nguồn sự thật.

Rate limit theo nhiều tín hiệu giúp giảm retry hoặc bot quá mức. Waiting room cấp vé và điều tiết số lượt được vào bước mua. Vé cần được kiểm tra phía server, có thời hạn và chống dùng lại ngoài quyền đã cấp; client countdown không phải kiểm soát tải.

Admission rate phải bám vào capacity downstream, quan sát latency, lỗi và hàng đợi. Không có quy tắc mỗi tầng giảm đúng mười lần. Nếu database chỉ an toàn ở một mức thấp hơn, waiting room phải giữ luồng vào tương ứng và trả trạng thái minh bạch cho phần còn lại.

Trừ kho không oversell

Sơ đồ: Trừ kho không oversell

Xem sơ đồ đầy đủ: Trừ kho không oversell

Reservation giữ quyền mua tạm thời. Một thao tác Redis Lua hoặc Function có thể kiểm tra tồn, kiểm tra user, giảm phần giữ chỗ và tạo reservation cùng lúc. Trong Redis Cluster, các key liên quan phải đáp ứng ràng buộc cùng slot của thao tác; script cần ngắn để tránh chặn lâu. DECR riêng lẻ vẫn có thể trả số âm, nên nguyên tử chưa đủ nếu thiếu điều kiện.

Sau admission, database xác nhận bằng cập nhật tồn có điều kiện, ghi order và outbox trong cùng transaction. Nếu không đủ tồn hoặc reservation không hợp lệ, giao dịch không được báo thành công. Redis failover có thể làm thông tin giữ chỗ lệch, nên database là chốt cuối giữ bất biến tồn trong thiết kế này.

Reservation hết hạn cần worker chuyển trạng thái và hoàn giữ chỗ idempotent. TTL tự xóa key không tự cộng lại tồn. Race giữa thanh toán và hết hạn phải được quyết định bằng state transition có điều kiện. Phản hồi cho client phân biệt đang giữ chỗ, chờ xác nhận và đơn đã xác nhận để retry không gây mua trùng.

Kiến trúc không đứng yên

Sơ đồ: Kiến trúc không đứng yên

Xem sơ đồ đầy đủ: Kiến trúc không đứng yên

Giai đoạn đầu thường cần nhanh hiểu sản phẩm và giữ hệ thống đơn giản. Khi đội và dữ liệu lớn hơn, khả năng bảo trì, quyền sở hữu và capacity trở nên quan trọng hơn. Các giai đoạn đơn giản hóa, bảo trì, tăng trưởng rồi quản lý độ phức tạp giúp nhìn sự thay đổi trọng tâm, không phải lịch trình mọi sản phẩm buộc phải đi qua.

Hoãn quyết định nghĩa là giữ những lựa chọn đắt và khó đảo ngược tới lúc có đủ thông tin, đồng thời chuẩn bị ranh giới để thay đổi được. Không phải hoãn những việc nền tảng như kiểm soát quyền, quản lý dữ liệu hay quan sát lỗi.

Có hai bẫy: trả chi phí phức tạp quá sớm và giữ giải pháp cũ quá lâu sau khi số liệu đã chứng minh nó không đáp ứng. Hãy đặt ngưỡng xem lại thiết kế, đo định kỳ và ghi quyết định. Kiến trúc tốt gắn với bối cảnh hiện tại và một đường tiến hóa khả thi.

Tổng kết khóa & mock interview

Chúng ta quay lại bốn bước: làm rõ yêu cầu, ước lượng, thiết kế tổng thể và đào sâu. Những thành phần đã học chỉ có giá trị khi giúp trả lời ba câu: vì sao cần nó, phải đánh đổi gì, và có thể tổ chức lại để giảm đánh đổi không?

Hãy chọn một đề chưa luyện, chẳng hạn cộng tác tài liệu hoặc bán vé, và tự chạy một phiên bốn mươi phút. Ghi hình cả lời nói và sơ đồ. Sau đó xem lại: có nêu giả định không, API khớp chức năng không, các con số có ảnh hưởng lựa chọn không, và có đi qua ít nhất một failure path không?

Lần thứ hai, thay một ràng buộc: tăng tải mười lần, yêu cầu không mất bản ghi đã xác nhận hoặc giảm ngân sách một nửa. Nếu bạn biết sửa phần nào và giải thích vì sao, bạn đang dùng nguyên lý thay vì nhớ đáp án. Đó là năng lực cốt lõi khóa học hướng tới.

Reservation hỗ trợ admission, DB giữ bất biến cuối

1M request / 10 s = 100K QPS trung bình trong đợt
Đỉnh tức thời cần đo riêng.

CDN -> rate limit -> waiting room -> reservation
  -> DB transaction:
     kiểm tra reservation/trạng thái hợp lệ
     UPDATE inventory SET available = available - 1
       WHERE sku = :sku AND available > 0
     yêu cầu cập nhật đúng 1 dòng
     INSERT order với unique request/user theo chính sách
     INSERT outbox
     COMMIT

Worker hết hạn: pending -> expired bằng cập nhật có điều kiện
Hoàn giữ chỗ một lần; retry không cộng tồn lần nữa.

Lua kiểm tra và giảm tồn trên Redis cần giữ các key cùng slot khi dùng cluster, nhưng failover vẫn có thể làm reservation lệch. TTL chỉ xóa trạng thái theo cấu hình, không tự hoàn kho. DB xác nhận và luồng reconciliation mới quyết định kết quả cuối trong mô hình này.

Thuật ngữ cần nhớ

  • QPS — Queries Per Second: Số truy vấn mỗi giây; trong bài ước lượng cần nói rõ đang đếm truy vấn dữ liệu hay lượt gọi đọc. Trung bình và đỉnh là hai giá trị khác nhau.

  • CDN — Content Delivery Network: Mạng phân phối nội dung: đưa nội dung tới các máy biên gần người dùng để giảm độ trễ và tải về máy chủ gốc.

  • TTL — Time To Live: Thời gian sống còn hiệu lực của dữ liệu, bản ghi tên miền hoặc lease. Hết hạn không tự thực hiện nghiệp vụ như hoàn tồn kho; cần luồng xử lý tương ứng.

  • CAS — Compare-And-Swap: So sánh và đổi nguyên tử: chỉ thay giá trị nếu giá trị hiện tại khớp kỳ vọng; dùng trong thuật toán đồng thời, có thể phải retry khi tranh chấp.

Bài tập tự thực hành

Worker hoàn giữ chỗ chạy hai lần trong khi xác nhận đơn đang diễn ra.

Gợi ý kiểm tra lời giải

Chuyển trạng thái có điều kiện, idempotency, tồn DB là chốt cuối; TTL không tự hoàn kho.

Tự kiểm tra sau bài học

  • Admission bám capacity downstream thay vì hệ số giảm tải tùy ý.

  • Tồn không âm kể cả khi Redis mất trạng thái hoặc client retry.

  • Thanh toán, hết hạn và hoàn giữ chỗ tranh chấp bằng state transition có điều kiện.

Rubric tự chấm một phiên 40 phút

Mỗi tiêu chí chấm 0–2 điểm: 0 chưa có, 1 có nhưng mơ hồ, 2 có lập luận và cách kiểm chứng. Tổng tối đa 20 điểm, chỉ dùng để tự luyện.

  • Phạm vi: Chốt chức năng lõi, ngoài phạm vi và giả định còn thiếu.

  • NFR: Có mục tiêu đo, thứ tự ưu tiên theo luồng và hậu quả khi vi phạm.

  • Ước lượng: Đúng đơn vị, phân biệt trung bình/đỉnh, nối số liệu với lựa chọn.

  • API và dữ liệu: Access pattern khớp chức năng; nêu khóa, nguồn sự thật và bất biến.

  • Luồng chính: Đi được trọn một lần ghi và đọc, chỉ rõ sync/async và thời điểm xác nhận.

  • Đồng thời: Xử lý retry, race hoặc duplicate ở nơi thực sự cần.

  • Sự cố: Đi qua một lỗi từng phần và giải thích recovery, không chỉ nói “thêm replica”.

  • Đánh đổi: So ít nhất một phương án khác và điều kiện khiến đổi quyết định.

  • Quan sát: Nêu metric, log/trace hoặc cách đo chứng minh mục tiêu đạt.

  • Giao tiếp: Giữ đúng thời gian, sơ đồ có nhãn, giả định được nói thành lời.

Đọc thêm từ tài liệu gốc

Redis: Scripting with Lua

Tiếp tục lộ trình

Bài trước — Thiết kế ví điện tử: ledger, idempotency và đối soát

Xem toàn bộ khóa học System Design 21 bài

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 76: Circuit Breaker với Resilience4j

CLOSED, OPEN, HALF_OPEN, fallback, bulkhead và rate limiter để giới hạn lỗi dây chuyền.

10 thg 10, 20262 phút5
99 Ngày Java — Ngày 76: Soft, Weak và Phantom Reference

Bốn mức tham chiếu, WeakHashMap, ReferenceQueue và cách chọn cache có giới hạn trên Java 21.

10 thg 10, 20262 phút6
K

Học System Design qua 21 bài: yêu cầu, capacity, database, cache, hệ phân tán và 6 bài thực hành, kèm bài tập, sơ đồ luồng và mục lục đầy đủ.

10 thg 10, 20266 phút3