Bí ẩn kiến trúc Flash Sale: Bán hàng triệu đơn trong 1 giây mà không sập hệ thống
Trong các đợt siêu sale ngày đôi (11/11, 12/12) của Shopee, Lazada hay Tiki, có những sản phẩm giảm giá cực sâu nhưng số lượng chỉ có 100 chiếc. Hàng triệu người dùng cùng lúc bấm "Mua Hàng" đúng vào lúc 00:00:00.
Vậy làm thế nào để hệ thống chọn ra đúng 100 người mua thành công mà:
- Không bán lố thành 101 người (Overselling).
- Hệ thống không bị sập (Crash) do lượng Request khổng lồ.
- Phản hồi kết quả cực nhanh trong vài mili-giây. Bài viết này sẽ bóc tách kiến trúc phía sau một hệ thống Flash Sale tiêu chuẩn.
1. Vấn đề của Database Truyền Thống (RDBMS)
Nếu bạn thiết kế hệ thống bình thường, logic mua hàng sẽ như sau:
Bắt đầu Transaction. SELECT stock FROM products WHERE id = 1 FOR UPDATE Nếu stock > 0, cho phép mua. UPDATE products SET stock = stock - 1 WHERE id = 1 Commit Transaction. Tại sao cách này "chết chắc" trong Flash Sale?
Row-level Lock: Khi dùng FOR UPDATE, Database sẽ khóa (lock) dòng dữ liệu đó lại. Nếu có 1.000.000 requests cùng lúc lao vào 1 dòng dữ liệu, 999.999 requests còn lại sẽ phải xếp hàng chờ đợi (Lock Contention).
Overload: Connection Pool của Database (PostgreSQL/MySQL) thường chỉ giới hạn ở mức vài trăm đến vài nghìn. 1 triệu request sẽ làm treo cứng (hang) Database ngay lập tức.
Tốc độ: Truy xuất xuống ổ cứng (Disk I/O) quá chậm để xử lý hàng triệu request mỗi giây.
2. Sơ đồ Kiến trúc Hệ thống Flash Sale
Để xử lý bài toán này, chúng ta cần tách biệt tầng Kiểm tra tồn kho (In-memory) và tầng Lưu trữ đơn hàng (Persistent), được kết nối với nhau qua Message Queue.
Dưới đây là sơ đồ luồng dữ liệu (Sequence Diagram) khi 1 User bấm nút mua hàng:
Nhìn vào sơ đồ trên, bạn sẽ thấy Database (PostgreSQL) hoàn toàn không phải chịu áp lực từ hàng triệu người dùng bấm mua. Áp lực đó đã được chặn đứng bởi Redis.
3. "Cứu Tinh" Redis và Thao tác Atomic
Để giải quyết bài toán tốc độ, toàn bộ kho hàng (stock) của sản phẩm Flash Sale phải được đưa lên bộ nhớ RAM. Redis chính là lựa chọn số 1.
Trước khi Flash Sale diễn ra, hệ thống sẽ nạp số lượng sản phẩm vào Redis:
SET flash_sale:iphone_15:stock 100
Khi có người dùng bấm mua, chúng ta dùng lệnh DECR (Decrement) của Redis:
DECR flash_sale:iphone_15:stock
Tại sao Redis giải quyết được?
In-Memory: Redis lưu dữ liệu trên RAM, tốc độ đọc/ghi cực nhanh (micro-giây). Single-Threaded: Redis thực thi các lệnh tuần tự trên một luồng duy nhất. Lệnh DECR là một thao tác Atomic (không thể chia nhỏ). Do đó, nó ngăn chặn tuyệt đối tình trạng Race Condition. Nếu stock giảm xuống nhỏ hơn 0, hệ thống báo "Hết hàng". Tuyệt đối không bao giờ có chuyện 101 người mua được.
4. Message Queue: "Bộ Giảm Xóc" Cho Hệ Thống
Redis giúp kiểm tra tồn kho nhanh, nhưng sau khi biết user mua thành công, hệ thống còn phải làm rất nhiều việc nặng nề khác: Tạo đơn hàng, tính toán mã giảm giá, phí ship, gửi email xác nhận.
Nếu làm tất cả việc này trực tiếp (Synchronous), API sẽ phải chờ rất lâu. Giải pháp là sử dụng Message Queue (như Kafka, RabbitMQ).
Lợi ích của Queue:
Peak Shaving (Cắt đỉnh tải): 1 triệu request có thể vào Queue ngay lập tức, và Order Worker cứ từ từ lôi ra xử lý 1000 đơn/giây đổ vào Database. Hệ thống không bao giờ bị ngộp. Decoupling (Tách rời): Dịch vụ Đặt Hàng (Database) có sập thì tin nhắn vẫn nằm an toàn trong Queue, khi nào Database sống lại thì xử lý tiếp, không bị mất đơn.
5. Vượt Qua Nút Thắt Mạng (CDN & Caching)
Trong Flash Sale, 99.9% người dùng vào web chỉ để... nhìn màn hình đếm ngược (Countdown) hoặc tải hình ảnh sản phẩm. Chỉ một phần rất nhỏ thực sự nhấn nút Mua.
Giải pháp Frontend & Gateway:
CDN (Content Delivery Network): Tất cả hình ảnh, CSS, JS, HTML tĩnh phải được đẩy ra CDN. Người dùng tải dữ liệu từ server gần nhất, Server gốc không hề hấn gì. Lazy Loading Nút Mua: Nút "Mua Ngay" ban đầu mờ đi. Đến đúng 00:00:00, Frontend chỉ gọi một API siêu nhỏ để đồng bộ giờ chuẩn, sau đó tự bật sáng. Chống Spam (Rate Limiting): Tại API Gateway, nếu 1 User (cùng IP/Token) bấm mua quá 5 lần/giây, chặn ngay lập tức để chống tool/bot lạm dụng.
6. Kết Luận
Một hệ thống Flash Sale là đỉnh cao của sự kết hợp nhịp nhàng:
CDN/Cache: Chặn 90% lượng traffic xem tĩnh. Redis: Trực tiếp đón đầu lượng request Mua khổng lồ, trừ tồn kho an toàn tuyệt đối. Message Queue: Gánh tải (buffer), đảm bảo Database được "thở". Database (RDBMS): Chỉ đóng vai trò lưu trữ kết quả cuối cùng một cách chậm rãi, chính xác. Áp dụng đúng các kiến trúc này, bạn hoàn toàn có thể tự tin vượt qua các đợt traffic khổng lồ mà không sợ "sập server"!
All rights reserved