Backend Development 18 min read

Designing a High-Concurrency Ticket Booking System: Architecture, Load Balancing, and Go Implementation

This article explores the design of a high‑concurrency train ticket‑seckill system, covering 12306 backend architecture, multi‑layer load balancing, Redis‑based stock deduction, Go code examples, performance testing, and strategies to prevent overselling and ensure fault tolerance.

Laravel Tech Community
Laravel Tech Community
Laravel Tech Community
Designing a High-Concurrency Ticket Booking System: Architecture, Load Balancing, and Go Implementation

During holidays, many people in major Chinese cities face the problem of抢票 (ticket grabbing) on the 12306 train ticket platform, which experiences extreme QPS and millions of concurrent requests.

The author studies the 12306 backend architecture and shares a simulated example of handling 1 million users competing for 10 000 tickets while keeping the service stable.

Key components of a large‑scale high‑concurrency system include distributed clusters, multiple layers of load balancers (OSPF, LVS, Nginx), and fault‑tolerance mechanisms such as dual data centers.

Nginx is used for three load‑balancing methods—round‑robin, weighted round‑robin, and IP‑hash. An example of weighted round‑robin configuration is shown:

#配置负载均衡
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;
    }
}

Four local Go HTTP services (ports 3001‑3004) are started; each logs requests to ./stat.log. The AB tool is used to generate 1 000 requests with 100 concurrent connections, confirming that each port receives the expected proportion of traffic (1, 2, 3, 4).

To avoid database bottlenecks, the design adopts a pre‑deduction (pre‑stock) strategy: each server keeps a local inventory in memory, reduces it atomically, and only when local stock is sufficient does it invoke a remote Redis stock‑deduction.

Redis stores the total inventory and sold count in a hash. The remote deduction is performed atomically with a Lua script:

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
`

The Go initialization sets local stock, Redis keys, and a channel used as a lightweight distributed lock. The request handler performs:

if localSpike.LocalDeductionStock() && remoteSpike.RemoteDeductionStock(redisConn) {
    // success response
} else {
    // sold out response
}

Performance testing on a low‑spec Mac shows the single‑node service handling over 4 000 requests per second, with uniform traffic distribution and no failed requests. Logs confirm correct stock updates and graceful handling of sold‑out conditions.

In summary, the article demonstrates how load balancing, in‑memory pre‑deduction, Redis atomic operations, and Go’s concurrency model can build a scalable ticket‑seckill system that avoids DB I/O, prevents overselling, tolerates node failures, and fully utilizes server CPU.

load balancingRedisGohigh concurrencynginxticket-booking
Laravel Tech Community
Written by

Laravel Tech Community

Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.

0 followers
Reader feedback

How this landed with the community

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