Designing a High-Concurrency Ticket Spike System with Go, Nginx, and Redis

This article explains how to build a high‑concurrency ticket‑seckill system using Go, Nginx weighted load balancing, and Redis, covering architecture design, local and remote stock deduction, Lua scripting for atomic operations, and performance testing with ApacheBench to achieve thousands of requests per second without overselling.

Big Data Technology & Architecture
Big Data Technology & Architecture
Big Data Technology & Architecture
Designing a High-Concurrency Ticket Spike System with Go, Nginx, and Redis

During holiday travel peaks, millions of users compete for train tickets, creating a massive QPS challenge similar to a global flash‑sale system. The article examines the 12306 ticketing service architecture and demonstrates how to design a scalable spike system that can handle 1 million concurrent users buying 10 000 tickets.

1. Large‑Scale Architecture – A distributed cluster with multiple load‑balancing layers (OSPF, LVS, Nginx) spreads traffic across servers. A simple diagram illustrates the flow from user request to backend services.

1.1 Load Balancing Overview – Three common methods are described:

OSPF: internal gateway protocol that can perform equal‑cost multi‑path routing.

LVS: IP‑level load balancer that distributes connections across a server pool.

Nginx: HTTP reverse proxy supporting round‑robin, weighted round‑robin, and IP‑hash.

1.2 Nginx Weighted Round‑Robin Demo

# Configuration of weighted upstream
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;
    }
}

Requests are distributed according to the configured weights, which matches the observed request counts (100, 200, 300, 400) in the test.

2. Spike System Design Options

The article compares three ordering strategies:

Order‑then‑deduct : create an order first, then reduce inventory. Guarantees no oversell but incurs heavy DB I/O and risks “no‑pay” loss.

Pay‑then‑deduct : wait for payment before reducing stock. Prevents loss but can cause oversell under extreme concurrency.

Pre‑deduct (reserve) stock : reserve inventory first, generate orders asynchronously, and release reserved stock after a timeout. This reduces DB pressure and handles high QPS.

Pre‑deduct is chosen as the most practical solution.

3. Stock Deduction Techniques

Local in‑memory stock is used to avoid frequent DB writes. Each server holds a portion of the total inventory and updates a local counter:

func (spike *LocalSpike) LocalDeductionStock() bool {
    spike.LocalSalesVolume = spike.LocalSalesVolume + 1
    return spike.LocalSalesVolume < spike.LocalInStock
}

If local stock is insufficient, a remote deduction is performed in Redis using an atomic Lua script:

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

Redis stores the total inventory and sold count in a hash (e.g., ticket_hash_key), initialized with:

hmset ticket_hash_key "ticket_total_nums" 10000 "ticket_sold_nums" 0

4. Go Implementation

Initialization ( init()) sets local stock, Redis keys, and a channel used as a lightweight distributed lock:

func init() {
    localSpike = LocalSpike{LocalInStock: 150, LocalSalesVolume: 0}
    remoteSpike = RemoteSpikeKeys{SpikeOrderHashKey: "ticket_hash_key", TotalInventoryKey: "ticket_total_nums", QuantityOfOrderKey: "ticket_sold_nums"}
    redisPool = remoteSpike.NewPool()
    done = make(chan int, 1)
    done <- 1
}

The HTTP handler processes a purchase request, performs local and remote deductions atomically, returns JSON success/failure, logs the result, and releases the channel lock:

func handleReq(w http.ResponseWriter, r *http.Request) {
    redisConn := redisPool.Get()
    LogMsg := ""
    <-done
    if localSpike.LocalDeductionStock() && remoteSpike.RemoteDeductionStock(redisConn) {
        RespJson(w, 1, "抢票成功", nil)
        LogMsg = LogMsg + "result:1,localSales:" + strconv.FormatInt(localSpike.LocalSalesVolume, 10)
    } else {
        RespJson(w, -1, "已售罄", nil)
        LogMsg = LogMsg + "result:0,localSales:" + strconv.FormatInt(localSpike.LocalSalesVolume, 10)
    }
    done <- 1
    writeLog(LogMsg, "./stat.log")
}

The server is started on a configurable port (e.g., :3005).

4.4 Performance Test

Using ApacheBench ( ab -n 10000 -c 100 http://127.0.0.1:3005/buy/ticket) the system handled ~4 000 requests per second on a low‑end Mac, with zero failures and latency around 23 ms per request. Log output shows the gradual increase of local sales until stock is exhausted.

5. Summary

The article demonstrates a practical high‑concurrency spike system that avoids database bottlenecks by combining local in‑memory stock, Redis atomic operations, and weighted Nginx load balancing. It highlights two key lessons: distributing load across many servers and leveraging asynchronous, lock‑free designs (e.g., Go goroutines, epoll‑based servers) to fully utilize CPU resources.

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.

GoNGINXdistributed-systemsticket seckillhigh-concurrencyload-balancing
Big Data Technology & Architecture
Written by

Big Data Technology & Architecture

Wang Zhiwu, a big data expert, dedicated to sharing big data technology.

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.