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.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
How to Build a Million‑User Ticket‑Snatching System with Go, Nginx, and Redis

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 <- 1

HTTP 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/ticket

On 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
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.

redisGohigh concurrencyticketing system
Java High-Performance Architecture
Written by

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.

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.