Designing a Million-User Flash Sale System: Lessons from a Spring Festival Red‑Envelope Project
This article walks through the core contradictions of flash‑sale systems, presents a layered architecture using CDN, Nginx, Redis, MQ and a database, explains key implementation details and common pitfalls, and offers incremental optimization steps for building a robust, high‑concurrency service.
Hello, I'm Yu Wan. Many friends ask how to answer an interview question that asks you to design a flash‑sale (秒杀) system. I recently rebuilt a flash‑sale system for a Spring Festival red‑envelope project that handled peak traffic, and I’ll share the core ideas.
1. What is the essence of a flash‑sale?
The fundamental conflict is limited inventory vs massive requests . For example, 100 items and 100,000 users – only 100 can succeed, the rest are “spectators”. The design goals are:
Protect the system from crashing (prevent 100,000 users from overloading the servers).
Ensure data consistency (avoid overselling or underselling).
The philosophy is: under the premise of correctness, filter out as many invalid requests as possible .
2. What does a classic flash‑sale system look like?
Client → CDN → Reverse Proxy (Nginx) → Web Application → Redis → MQ → Database
Each layer has its own responsibility.
Layer 1: Traffic entry – CDN and Nginx
Static assets (HTML, CSS, JS, images) are placed on CDN so they never hit the application servers.
Rate limiting : limit_req_zone limits each IP to, for example, 5 requests per second.
Load balancing : distributes requests evenly across backend web servers.
Layer 2: Web application – the real filter
The core logic lives here. Direct DB access would die under load. The rule is: if it can be done in memory, never touch disk.
Key operation: Redis pre‑decrement inventory
Long remaining = redisTemplate.opsForValue().decrement("seckill:stock:1001");
if (remaining < 0) {
// 库存不足,直接返回“已售罄”
return "已售罄";
} DECRis atomic, preventing oversell and filtering out about 90% of requests.
Small optimization : after stock is exhausted, set a flag in Redis so subsequent DECR calls are skipped.
Layer 3: Message queue – asynchronous peak‑shaving
After Redis filtering, the remaining “potentially successful” requests are not written to the DB immediately; they are sent to a MQ (RocketMQ/Kafka). The web app returns “in queue, please wait”.
Peak shaving : smooths sudden traffic spikes.
Decoupling : the order system can fail without affecting the flash‑sale service.
Layer 4: Database – final consistency
Consumers pull messages from MQ, write orders to the DB, and deduct inventory as a safety net.
Database update uses an optimistic lock:
UPDATE product SET stock = stock - 1 WHERE id = 1001 AND stock > 0;The affected‑row count indicates success (1) or that inventory has already been taken (0).
3. Three common pitfalls
Pitfall 1: Oversell
Symptom: sold 100 items but inventory shows –5.
Cause: concurrent DB updates without proper locking (e.g., missing SELECT ... FOR UPDATE).
Solution: Redis pre‑decrement + DB optimistic lock provides double protection.
Pitfall 2: Duplicate orders
Symptom: the same user succeeds twice.
Cause: rapid clicks or client retries let multiple requests pass Redis validation.
Solution: enforce per‑user purchase limits in Redis:
// 抢购前检查
String userKey = "seckill:user:1001:" + userId;
if (redisTemplate.hasKey(userKey)) {
return "您已参与过";
}
// DECR成功后,设置用户标记,过期时间稍长
redisTemplate.opsForValue().set(userKey, "1", 30, TimeUnit.MINUTES);Pitfall 3: Page white‑screen or slow response
Symptom: page fails to load at the start of the event.
Cause: Tomcat thread pool exhausted, often because Redis connection pool is full.
Solutions:
Deploy the flash‑sale interface on dedicated servers, separate from normal product pages.
Configure reasonable thread pools and time‑outs; fast‑fail is better than long waits.
Use CompletableFuture for asynchronous processing to avoid blocking the main thread.
4. An extreme optimization: static page + disabled button
Before the event, the flash‑sale page is fully static on CDN and the “Buy Now” button is greyed out. The start time is hard‑coded in JS. When the moment arrives, JS enables the button, allowing users to click.
This prevents any pre‑emptive requests, blocking 99% of script‑based attacks. Advanced attackers can still tamper with parameters, so backend validation remains necessary.
5. Full process recap
User clicks “Buy”.
Nginx rate‑limits; excess requests receive “Too frequent”.
Web layer checks if the user has already bought (Redis).
Web layer executes DECR in Redis to reduce stock.
If DECR succeeds, send an MQ message and immediately return “Purchase successful” or “In queue”.
MQ consumer asynchronously writes the order to the database.
User polls for order status and eventually receives the order number.
Only step 4 touches Redis (very fast) and step 5 writes to MQ (very fast). Database work is asynchronous, so the flash‑sale API typically responds within 10 ms and can handle tens of thousands of requests per second.
6. Final thoughts
The flash‑sale system is not magical; its core is layer‑by‑layer filtering : CDN filters static traffic, Nginx filters IPs, Redis filters inventory, MQ smooths peaks, and the database provides a safety net.
Recommendations for hands‑on practice:
Build the simplest version first: Redis DECR + DB write.
Run a JMeter load test to see at what concurrency the system breaks.
Gradually introduce Nginx rate‑limiting and MQ asynchronous processing, comparing benchmark results after each step.
Practice coding the whole flow beats reading the article ten times.
Question for discussion: If the flash‑sale item is a virtual coupon with massive inventory, how would you adjust this architecture?
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Coder Trainee
Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
