Designing a High‑Concurrency Ticket Spike System with Load Balancing, Nginx, and Go

This article explains how to design and implement a high‑concurrency ticket‑seckill system by analyzing the 12306 architecture, introducing multi‑layer load balancing, demonstrating Nginx weighted round‑robin configuration, and providing Go and Redis code to achieve stable, low‑latency ticket purchasing under massive simultaneous requests.

Architect
Architect
Architect
Designing a High‑Concurrency Ticket Spike System with Load Balancing, Nginx, and Go

During holiday periods, millions of users compete for train tickets, creating a massive QPS load that the 12306 service must handle; the author studies its architecture and presents a simulated example of serving 1 million users buying 10 000 tickets.

Large‑scale high‑concurrency architecture – Distributed clusters with multiple layers of load balancers, dual‑datacenter redundancy, and traffic distribution are illustrated with diagrams.

Load‑balancing overview – Three common methods are described: OSPF (cost‑based routing), LVS (Linux Virtual Server), and Nginx (HTTP reverse proxy) with a focus on weighted round‑robin.

Nginx weighted round‑robin demo

#配置负载均衡
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 author configures a local hosts entry for www.load_balance.com and writes four Go services listening on ports 3001‑3004.

package main
import (
    "net/http"
    "os"
    "strings"
)
func main() {
    http.HandleFunc("/buy/ticket", handleReq)
    http.ListenAndServe(":3001", nil)
}
// handle request and write log
func handleReq(w http.ResponseWriter, r *http.Request) {
    failedMsg := "handle in port:"
    writeLog(failedMsg, "./stat.log")
}
func writeLog(msg string, logPath string) {
    fd, _ := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
    defer fd.Close()
    content := strings.Join([]string{msg, "
"}, "")
    fd.Write([]byte(content))
}

ApacheBench (ab) is used to generate load, confirming that a single instance can process over 4 000 requests per second.

Seckill system design choices – The article compares three inventory‑deduction strategies (order‑then‑deduct, pay‑then‑deduct, pre‑deduct) and concludes that pre‑deduction with local memory stock plus a Redis‑backed global stock provides the best balance of performance and consistency.

Local stock is managed in memory, while Redis stores a hash with total inventory and sold count. A Lua script guarantees atomic deduction:

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 (RemoteSpikeKeys *RemoteSpikeKeys) RemoteDeductionStock(conn redis.Conn) bool {
    lua := redis.NewScript(1, LuaScript)
    result, err := redis.Int(lua.Do(conn, RemoteSpikeKeys.SpikeOrderHashKey, RemoteSpikeKeys.TotalInventoryKey, RemoteSpikeKeys.QuantityOfOrderKey))
    if err != nil { return false }
    return result != 0
}

Initialization sets the Redis hash:

hmset ticket_hash_key "ticket_total_nums" 10000 "ticket_sold_nums" 0

. The request handler first performs local deduction, then remote deduction, returning success or “sold out” and logging the outcome.

Load‑testing with ab -n 10000 -c 100 http://127.0.0.1:3005/buy/ticket shows stable latency and no failed requests, confirming the design’s ability to handle high traffic.

Conclusion – By combining load balancing, in‑memory stock, Redis atomic operations, and Go’s concurrency model, the system avoids database bottlenecks, prevents overselling and underselling, and tolerates partial node failures, offering practical insights for building robust high‑concurrency services.

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 concurrencyNginx
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

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.