How 12306 Handles Millions of Ticket Requests: High‑Concurrency Architecture Explained
This article analyzes the extreme traffic spikes of China's 12306 train ticket platform, detailing its distributed high‑concurrency architecture, load‑balancing strategies, inventory‑deduction techniques, and Go/Redis code examples that enable stable, oversell‑free ticket purchasing during peak holidays.
Background
During holidays, millions of users in China try to purchase train tickets through the 12306 platform, creating a massive spike in QPS that can reach millions of requests per second.
1. Large‑scale high‑concurrency architecture
The system is deployed as a distributed cluster with multiple layers of load balancers and disaster‑recovery mechanisms (dual data centers, node fault tolerance, backup servers) to ensure high availability.
1.1 Load‑balancing overview
Three typical load‑balancing technologies are described:
OSPF – an interior gateway protocol that builds a link‑state database and can perform up to six‑path equal‑cost load balancing.
LVS – Linux Virtual Server, a cluster solution that distributes requests across servers and masks failures.
Nginx – a high‑performance HTTP reverse proxy that supports round‑robin, weighted round‑robin, and IP‑hash algorithms.
1.2 Nginx weighted round‑robin demo
# Configuration of load balancing
upstream load_rule {
server 127.0.0.1:3001 weight=1;
server 127.0.0.1:3002 weight=2;
server 127.0.0.1:3003 weight=3;
server 127.0.0.1:3004 weight=4;
}
...
server {
listen 80;
server_name load_balance.com www.load_balance.com;
location / {
proxy_pass http://load_rule;
}
}Local Go services for testing
package main
import ("net/http" "os" "strings")
func main() {
http.HandleFunc("/buy/ticket", handleReq)
http.ListenAndServe(":3001", nil)
}
func handleReq(w http.ResponseWriter, r *http.Request) {
// write log, etc.
}Using ab -n 1000 -c 100 http://www.load_balance.com/buy/ticket the four ports received 100, 200, 300, and 400 requests respectively, matching the configured weights.
2. Seckill system design choices
The ticket‑seckill process involves three stages: order creation, inventory deduction, and payment. Three strategies are compared:
Order‑first then inventory – simple but creates heavy DB write load under extreme concurrency.
Payment‑first then inventory – avoids underselling but can cause overselling and still incurs DB I/O.
Pre‑deduct inventory – reserves stock in memory, performs order creation asynchronously, and uses a timeout to release unsold tickets.
Orders are typically placed into a message queue (e.g., Kafka) for asynchronous processing.
3. The art of inventory deduction
Local in‑memory stock is combined with a centralized Redis store. Each machine holds a portion of the total tickets; when local stock is exhausted, the request falls back to Redis. A “buffer” stock on each node tolerates node failures.
Redis can handle up to 100 k QPS, and because most requests succeed locally, the number of remote Redis calls remains low.
4. Code demonstration (Go)
4.1 Initialization
type LocalSpike struct {
LocalInStock int64
LocalSalesVolume int64
}
type RemoteSpikeKeys struct {
SpikeOrderHashKey string
TotalInventoryKey string
QuantityOfOrderKey string
}
func NewPool() *redis.Pool { /* ... */ }
func init() {
localSpike = LocalSpike{LocalInStock:150}
remoteSpike = RemoteSpikeKeys{
SpikeOrderHashKey:"ticket_hash_key",
TotalInventoryKey:"ticket_total_nums",
QuantityOfOrderKey:"ticket_sold_nums",
}
redisPool = NewPool()
done = make(chan int,1)
done <- 1
}4.2 Local and remote deduction
func (spike *LocalSpike) LocalDeductionStock() bool {
spike.LocalSalesVolume++
return spike.LocalSalesVolume < spike.LocalInStock
}
const LuaScript = ` local ticket_key = KEYS[1]
local ticket_total_key = ARGV[1]
local ticket_sold_key = ARGV[2]
local ticket_total_nums = tonumber(redis.call('HGET', ticket_key, ticket_total_key))
local ticket_sold_nums = tonumber(redis.call('HGET', ticket_key, ticket_sold_key))
if(ticket_total_nums >= ticket_sold_nums) then
return redis.call('HINCRBY', ticket_key, ticket_sold_key, 1)
end
return 0`
func (r *RemoteSpikeKeys) RemoteDeductionStock(conn redis.Conn) bool {
lua := redis.NewScript(1, LuaScript)
result, err := redis.Int(lua.Do(conn, r.SpikeOrderHashKey, r.TotalInventoryKey, r.QuantityOfOrderKey))
if err != nil { return false }
return result != 0
}4.3 HTTP handler
func handleReq(w http.ResponseWriter, r *http.Request) {
redisConn := redisPool.Get()
<-done
if localSpike.LocalDeductionStock() && remoteSpike.RemoteDeductionStock(redisConn) {
RespJson(w,1,"抢票成功",nil)
} else {
RespJson(w,-1,"已售罄",nil)
}
done <- 1
writeLog(...)
}4.4 Load testing
ab -n 10000 -c 100 http://127.0.0.1:3005/buy/ticketThe test shows the single‑node service handling over 4 000 requests per second with stable latency.
5. Summary
The article demonstrates a high‑concurrency ticket‑seckill system that avoids database I/O by performing most calculations in memory, uses Redis for centralized stock control, and employs load balancing and buffer stock to prevent overselling, underselling, and node failures.
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.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
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.
