Designing a High-Concurrency Flash Sale System with Redis Caching and Lua Scripts

This article explains how to build a high‑concurrency flash‑sale (秒杀) system by separating static page traffic with CDN, using read‑write‑split Redis for early request filtering, applying Lua scripts for atomic inventory deduction, and employing Redis as a simple message queue for asynchronous order persistence.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Designing a High-Concurrency Flash Sale System with Redis Caching and Lua Scripts

Flash‑sale (秒杀) activities sell scarce or discounted items in a short, timed window, generating traffic spikes that can be dozens or hundreds of times higher than normal; a well‑designed system improves stability, fairness, and user experience.

The flash‑sale process can be divided into three stages: pre‑sale (continuous page refreshes), sale start (burst of order requests), and post‑sale (successful orders refresh their status while most users keep refreshing the product page).

To prevent the database from being overwhelmed, the architecture intercepts invalid traffic at multiple layers. First, the flash‑sale product page is separated from regular pages and static elements are cached in the browser and CDN, so only a small fraction of requests reach the backend.

Second, a read‑write‑split Redis cluster is used to filter traffic early. The data‑control module pre‑loads the following keys in Redis:

"goodsId_count": 100   // total stock
"goodsId_start": 0     // start flag (0 = not started)
"goodsId_access": 0    // number of successful orders

Before the sale starts, the service reads goodsId_start (value 0) and returns “not started”. When the sale begins, the control module sets goodsId_start to 1, allowing requests to be processed. Each incoming request increments goodsId_access and the remaining stock is calculated as goodsId_count - goodsId_access. Once goodsId_access reaches goodsId_count, all further requests are blocked.

For inventory deduction, a master‑slave Redis setup is used to avoid direct database writes. A Lua script guarantees atomicity of the deduction operation:

local n = tonumber(ARGV[1])
if not n or n == 0 then return 0 end
local vals = redis.call("HMGET", KEYS[1], "Total", "Booked")
local total = tonumber(vals[1])
local booked = tonumber(vals[2])
if not total or not booked then return 0 end
if booked + n <= total then
    redis.call("HINCRBY", KEYS[1], "Booked", n)
    return n
end
return 0

The script is loaded once with SCRIPT LOAD and invoked via EVALSHA, which reduces network overhead compared with direct EVAL calls.

After a successful deduction, the order is placed into a Redis list acting as a simple message queue, enabling asynchronous persistence: LPUSH orderList {order_content} The consumer service retrieves orders with BRPOP orderList 0 and writes them to the database, preventing the database from becoming a bottleneck during massive sales.

The data‑control module periodically synchronizes data from the primary database to both the read‑write‑split Redis and the master‑slave Redis, ensuring that more traffic can be admitted as the sale progresses.

References to related articles are provided at the end of the original page.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Backend Architectureredishigh concurrencyMessage QueueLuaflash sale
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.