演唱会开票为什么总是崩?
三百万人同时点击同一个按钮,服务器没有崩溃,票没有超卖。这背后是一套精心设计的系统。
当某知名歌手的演唱会开票时,售票平台在那一刻瞬间涌入了数百万并发用户。对于多数人来说,那几分钟的体验大约是这样的:网页加载缓慢,点击「立即购票」后长时间转圈,然后看到一行字——「该场次已售罄」。
这是个工程奇迹,也是用户体验的灾难。但在多数情况下,系统并没有崩溃——它只是做了一件非常困难的事:在极短的时间内,尽量公平地处理了所有请求。
服务器为什么会崩
想象一台服务器是一个收银台,正常情况下每秒处理 10 笔交易没有问题。现在突然来了 300 万人同时排队,会发生什么?
直觉告诉我们:加更多收银台。但这里有一个更根本的问题——所有人都想买同一张票。无论有多少台服务器,最终写入「售出」这个状态的操作必须是串行的,因为票的数量是有限的。
技术上,这个问题叫做写争用(Write Contention)。当数千个请求同时尝试更新同一行数据库记录时,数据库需要对这些请求加锁、排队处理。锁的等待会导致请求堆积,连接池耗尽,内存溢出,最终服务崩溃。
水平扩展(加机器)在这里帮助有限:瓶颈不在请求的接收端,而在写入的那一行数据。
第一道防线:虚拟等候室
现代售票平台的第一个解法,是在用户真正进入购票流程之前,先让他们排队。
这个「虚拟等候室」(Virtual Queue)的原理并不复杂:用户点击购票时,系统立即返回一个排队号,并告知预计等待时间。真实的购票请求不会立即涌入系统,而是由后端按照固定的速率,逐批放行。
技术实现上,这通常用 Redis 完成。Redis 是一个极快的内存数据库,它的列表(List)结构支持原子性的入队出队操作,单实例每秒可以处理数十万次写入。每个用户进来后被压入队列,一个独立的调度器服务每隔一段时间从队头取出一批用户,发放令牌,允许他们继续购票。
虚拟队列的作用是削峰:把瞬间的洪峰,转化成系统可以承受的平稳水流。用户等待的时间,转移到了服务器的压力。
第二道防线:不卖超
即使有了排队机制,库存管理依然是最核心的问题:绝对不能卖超。
传统的做法是用数据库事务加锁:
BEGIN;
SELECT stock FROM tickets WHERE show_id = 1 FOR UPDATE;
-- 检查库存 > 0,然后扣减
UPDATE tickets SET stock = stock - 1 WHERE show_id = 1;
INSERT INTO orders (user_id, show_id) VALUES (?, ?);
COMMIT;
这个方案在逻辑上无懈可击,但在高并发下极慢——FOR UPDATE 会锁住整行,其他请求必须等待当前事务提交后才能继续。锁等待越来越长,请求越堆越多,最终形成雪崩。
更好的方案是把库存管理前移到 Redis。Redis 的 DECR 命令是原子的,意味着即使同时有一万个请求执行这个命令,Redis 也能保证每次操作是独立的,不会出现两个请求同时读到「剩余 1 张」然后都成功扣减的情况。
-- 这段 Lua 脚本在 Redis 中原子执行,相当于一次原子性的「检查 + 扣减」
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) > 0 then
redis.call('DECR', KEYS[1])
return 1 -- 扣减成功
else
return 0 -- 库存不足
end
Redis 扣减成功后,真正的数据库写入可以通过消息队列(如 Kafka)异步完成——慢慢写,不阻塞用户的购票请求。用户拿到「购票成功」的反馈,订单在几秒后才真正落库,这对用户来说是完全无感的。
CDN、限流与其他防线
在请求到达这些业务逻辑之前,还有几层过滤。
CDN 缓存:演唱会详情页、图片、样式文件都被缓存在 CDN 边缘节点。用户访问时,绝大部分静态内容直接由离用户最近的 CDN 节点返回,完全不触达源服务器。这让开票前的「围观流量」几乎无损耗地被吸收掉。
API 网关限流:即使有虚拟排队,API 网关也会设置每个用户 IP 的请求频率上限,防止脚本和黄牛机器人疯狂刷接口。常见方案是令牌桶(Token Bucket)算法——每个 IP 每秒只能发出固定数量的请求,超出的直接丢弃。
服务降级:当系统检测到压力过高时,可以主动关闭一些非核心功能(比如实时销量统计、推荐系统),把所有资源集中到「扣库存 + 生成订单」这条核心链路上。
余票显示的谎言
细心的用户可能会注意到,抢票时显示的「剩余票数」往往不准确——有时候写着「还有 50 张」,你刚要点购买,它变成了「已售罄」。
这不是 bug,是设计。
为了避免每次查询余票都读取最新库存(那会给数据库带来严重的读写竞争),系统会缓存一个近似值,每隔数秒更新一次。你看到的余票数,是几秒前的真实数字。在这几秒内,票可能已经卖完了。
这是分布式系统中「最终一致性」(Eventual Consistency)的典型应用:系统保证最终状态是正确的(不会真的超卖),但在某个短暂的时间窗口里,你看到的信息可能是过时的。强一致性可以做到,代价是大幅降低系统的并发能力——对于售票这个场景,这个代价不值得付。
系统没有崩
下次抢票遇到排队页面,不要急着关掉——那个等待页面,正是系统在保护自己,也在保护你的购票机会。
工程师们用数据结构和算法,让一件本质上「僧多粥少」的事,变得尽可能公平。他们没有办法凭空变出更多的票,但他们可以让系统在极限压力下不崩溃,让每一张票都准确地到达它应该到达的人手里。
服务器没有崩。票依然在第一秒售罄。
这两件事都是真的。