How to Build a Robust Flash‑Sale System: Tackling Oversell, High Concurrency, and Bot Attacks
This article explores the key challenges of designing a flash‑sale (秒杀) system—such as overselling, massive concurrent traffic, bot abuse, URL exposure, and database bottlenecks—and presents a comprehensive backend architecture with Redis clustering, Nginx load balancing, dynamic URLs, rate‑limiting, asynchronous order processing, and service degradation strategies.
Problems to consider in flash‑sale systems
Overselling
When inventory is limited (e.g., 100 items) but the system sells 200, the business suffers serious losses; preventing oversell is the top priority.
High concurrency
Flash sales last only minutes and attract huge traffic; the backend must handle sudden spikes without cache breakdown or database overload.
Interface abuse
Automated scripts can send hundreds of requests per second; the system needs to filter repeated invalid requests.
Flash‑sale URL
Exposed URLs allow savvy users to bypass the UI; the URL must be hidden or dynamically generated to prevent pre‑emptive requests.
Database design
Running flash‑sale traffic on the same database as other services risks cascading failures; isolation is required.
Massive request handling
Even with caching, a single Redis instance may only sustain ~40k QPS, while flash‑sale peaks can reach hundreds of thousands, leading to cache miss and DB overload.
Design and technical solutions
Flash‑sale database design
Create a dedicated flash‑sale database with two core tables: an order table and a product table, plus auxiliary tables for product details and user information.
Additional tables may store product metadata (images, names, prices) and user data (nickname, phone, address).
Dynamic flash‑sale URL
Generate the sale URL by MD5‑hashing a random string; the front end first requests the URL from the backend, which validates the request before exposing the URL.
Static page rendering
Render product description, parameters, transaction records, images, and reviews into a static HTML page using a template engine (e.g., FreeMarker) so the front end can serve the page without hitting the backend or database.
Redis cluster
Use Redis in sentinel mode or a clustered setup to improve performance and availability, mitigating cache‑penetration risks.
Using Nginx
Place Nginx in front of Tomcat clusters; Nginx can handle tens of thousands of concurrent connections, forwarding requests to multiple Tomcat instances.
Simplified SQL
Combine stock check and decrement into a single UPDATE statement with optimistic locking (version field) to avoid oversell and reduce round‑trips.
Redis pre‑decrement stock
Before the sale starts, set a Redis key with the total stock. Each successful order atomically decrements the key (via Lua script) and validates that stock never goes negative; cancelled orders increment the key.
Interface rate limiting
Filter invalid requests before they reach the database using multiple layers of rate limiting.
Frontend rate limiting
Disable the sale button for a few seconds after a click to reduce rapid repeat clicks.
Same user repeat request block
Use Redis key expiration (e.g., 10 seconds) per user ID; if a key exists, reject the request.
Token bucket algorithm
Apply a token bucket (e.g., Guava RateLimiter) to allow only a fixed number of requests per second; requests that cannot acquire a token are discarded.
public class TestRateLimiter {
public static void main(String[] args) {
// 1 token per second
final RateLimiter rateLimiter = RateLimiter.create(1);
for (int i = 0; i < 10; i++) {
// blocks until a token is available
double waitTime = rateLimiter.acquire();
System.out.println("Task " + i + " wait time " + waitTime);
}
System.out.println("Done");
}
}This example shows how the first task proceeds immediately, while subsequent tasks wait for the token to be produced.
public class TestRateLimiter2 {
public static void main(String[] args) {
final RateLimiter rateLimiter = RateLimiter.create(1);
for (int i = 0; i < 10; i++) {
long timeout = (long) 0.5;
boolean isValid = rateLimiter.tryAcquire(timeout, TimeUnit.SECONDS);
System.out.println("Task " + i + " valid: " + isValid);
if (!isValid) continue;
System.out.println("Task " + i + " executing");
}
System.out.println("End");
}
}Here tryAcquire attempts to get a token within 0.5 seconds; if it fails, the request is dropped, which is suitable for flash‑sale scenarios.
Asynchronous order processing
After rate limiting and stock verification, push the order request into a message queue (e.g., RabbitMQ) for asynchronous processing; successful orders can trigger SMS notifications, while failures can be retried via compensation mechanisms.
Service degradation
If a server crashes, fallback services (e.g., Hystrix circuit breaker) provide graceful degradation and user‑friendly messages instead of hard errors.
Summary
The following flowchart illustrates the end‑to‑end flash‑sale process, capable of handling hundreds of thousands of concurrent requests. For traffic in the tens of millions, further scaling such as database sharding, Kafka queues, and larger Redis clusters would be required.
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.
Efficient Ops
This public account is maintained by Xiaotianguo and friends, regularly publishing widely-read original technical articles. We focus on operations transformation and accompany you throughout your operations career, growing together happily.
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.
