How to Build a Million‑User Ticket‑Snatching System with Go, Nginx, and Redis
This article explores the architecture and implementation of a high‑concurrency ticket‑snatching system, covering load‑balancing strategies, Nginx weighted round‑robin configuration, Redis‑based inventory management, and Go code examples that together enable handling millions of simultaneous requests while preventing overselling and ensuring high availability.
12306 Ticket Snatching: Lessons from Extreme Concurrency
Although tickets can usually be booked, the moment tickets are released they often sell out instantly, especially during the Spring Festival when billions of users across the country compete for tickets using 12306, Zhixing, and other snatching apps.
The "12306 Service" faces a QPS that surpasses any flash‑sale system, handling millions of concurrent requests as a normal scenario.
The author studied the server‑side architecture of 12306 and shares a simulated example of how to provide stable service when 1 million users simultaneously try to buy 10 000 train tickets.
Large‑Scale High‑Concurrency System Architecture
High‑concurrency systems typically use distributed clusters, multiple layers of load balancers, and various disaster‑recovery mechanisms (dual data centers, node fault tolerance, server backup) to ensure high availability. Traffic is balanced across servers according to capacity and configuration.
Below is a simple diagram of such an architecture:
Load Balancing Overview
The diagram shows three layers of load balancing. The three types are:
OSPF (Open Shortest Path First) – an interior gateway protocol that builds a link‑state database and computes shortest‑path trees, allowing up to six equal‑cost paths for load balancing.
LVS (Linux Virtual Server) – a cluster technology that uses IP load balancing and content‑based request distribution, automatically masking server failures.
Nginx – a high‑performance HTTP reverse proxy that is widely used for load balancing.
Nginx supports three load‑balancing methods:
Round Robin
Weighted Round Robin
IP Hash
The following sections demonstrate Nginx weighted round‑robin configuration and testing.
Nginx Weighted Round Robin Demonstration
Weighted round robin is configured via the upstream module, assigning a weight to each backend server based on its performance or capacity.
Configuration example (listening on ports 3001‑3004 with weights 1, 2, 3, 4):
# Configure 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;
}
}The virtual domain www.load_balance.com is added to /etc/hosts.
Go Implementation of the Ticket Service
The service uses Go to demonstrate a single‑machine ticket‑snatching flow.
Local Stock Deduction
package localSpike
// Local stock deduction, returns bool
func (spike *LocalSpike) LocalDeductionStock() bool {
spike.LocalSalesVolume = spike.LocalSalesVolume + 1
return spike.LocalSalesVolume < spike.LocalInStock
}Remote Stock Deduction with Redis
package remoteSpike
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
}Service Initialization
... // Initialize local stock, remote Redis keys, and connection pool
localSpike = localSpike2.LocalSpike{LocalInStock: 150, LocalSalesVolume: 0}
remoteSpike = remoteSpike2.RemoteSpikeKeys{SpikeOrderHashKey: "ticket_hash_key", TotalInventoryKey: "ticket_total_nums", QuantityOfOrderKey: "ticket_sold_nums"}
redisPool = remoteSpike2.NewPool()
done = make(chan int, 1)
done <- 1HTTP Handler
package main
func handleReq(w http.ResponseWriter, r *http.Request) {
redisConn := redisPool.Get()
<-done
var LogMsg string
if localSpike.LocalDeductionStock() && remoteSpike.RemoteDeductionStock(redisConn) {
util.RespJson(w, 1, "抢票成功", nil)
LogMsg = "result:1,localSales:" + strconv.FormatInt(localSpike.LocalSalesVolume, 10)
} else {
util.RespJson(w, -1, "已售罄", nil)
LogMsg = "result:0,localSales:" + strconv.FormatInt(localSpike.LocalSalesVolume, 10)
}
done <- 1
writeLog(LogMsg, "./stat.log")
}Performance Testing
The service is started and stress‑tested with ApacheBench:
ab -n 10000 -c 100 http://127.0.0.1:3005/buy/ticketOn a low‑spec Mac the test reports over 4 000 requests per second, with uniform traffic distribution and stable Redis performance.
Key Takeaways
1. Load balancing and divide‑and‑conquer : Distribute traffic across many machines to fully utilize each server’s capacity.
2. Effective use of concurrency and asynchronous processing : Leveraging epoll‑based models (Nginx, Node.js, Redis) and Go’s goroutine concurrency enables high throughput while keeping the system simple.
3. Inventory management without heavy DB I/O : Local in‑memory stock deduction combined with a centralized Redis stock counter avoids frequent database writes, preventing overselling and ensuring high availability even when some nodes fail.
https://www.kancloud.cn/digest/understandingnginx/20260
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.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
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.
