# 在线票务 / 抢票 架构模板 > **代表产品 / 原型**:Ticketmaster、SeatGeek、大麦、12306,以及各类「秒杀」 > **一句话定位**:在开售瞬间海量用户争抢有限的座位 / 库存时,保证不超卖、相对公平、系统不被冲垮。 --- ## 1. 一句话定位 抢票系统 = **「极端瞬时并发」撞上「有限稀缺资源」的正面战场**。 百万人在同一秒抢几万张票。它是 [电商秒杀](../ecommerce-platform/README.md) 的极致版,还多了一个维度:**票常常是「具体的座位」**(3 排 12 号只有一个)。它的全部设计,都在同时满足三件几乎冲突的事:**绝不超卖、尽量公平、系统别崩。** ## 2. 业务本质:它在解决什么问题 它把**极度有限的资源(票 / 座位),在极度集中的时间(开售那一刻),卖给极度海量的人**。 这件事的特殊性在于「开售即洪峰」:平时没流量,开售瞬间流量涨几千倍,几分钟后又归于平静。为这种**尖刺**做架构,和为「平稳增长」做架构,思路完全不同。 > 三条铁律,排好优先级:**不超卖(底线)> 不崩(前提)> 公平(体验)。** 卖出一张不存在的票,是事故;系统崩了,谁也抢不到;不公平,口碑崩塌。 ## 3. 核心需求与约束 **功能性需求:** - [ ] 放票 / 库存管理(可能精确到座位) - [ ] 抢票:扣减库存 / 锁定座位 - [ ] 锁座(下单未支付时,先占住)+ 超时释放 - [ ] 下单、支付、出票 - [ ] 排队、限购、防刷 **非功能性需求 / 质量属性:** | 质量属性 | 目标 | 为什么对这类系统重要 | |---|---|---| | **不超卖** | 绝对 | 卖出不存在的票是重大事故 | | **峰值承载** | 抗住开售瞬间洪峰 | 平时低、开售瞬间爆,典型尖刺 | | **公平** | 先到先得 / 防机器人 | 被黄牛脚本扫光,口碑崩 | | **库存实时** | 余票 / 座位状态准 | 用户要看到「还剩几张」 | **关键约束(不可逾越的边界):** - 🔴 **库存有限且精确**:每个座位唯一,不能卖两次。 - 🔴 **并发极端且瞬时**:开售一瞬的写竞争集中在极少数「热门库存」上。 - 🔴 **存在「锁定中」中间态**:用户下单还没付,票既不能卖给别人、又不能永久占着。 - 🔪 **黄牛 / 脚本**:专业团伙用机器抢,公平性面临真实对抗。 ## 4. 架构全景图 ``` 开售瞬间:百万用户涌入 ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ┌──────────────────────────────────────────────┐ │ 虚拟等候室 / 排队(把洪峰挡在核心系统【外】) │ │ • 所有人先进等候室,拿一个排队号 │ │ • 按【系统能承受的速率】分批放行进入抢票 │ │ • 防刷:验证、限流、风控在这一层先筛一遍 │ └───────────────────────┬──────────────────────┘ ▼ 放行令牌(细水长流,而非洪峰漫灌) ┌──────────────────────────────────────────────┐ │ 抢票 / 库存服务 │ │ • 库存【原子扣减】(热点库存放内存,绝不超卖) │ │ • 锁定座位(临时占位) │ └───────────────────────┬──────────────────────┘ ▼ 抢到 → 生成订单(状态=待支付) ┌──────────────────────────────────────────────┐ │ 订单状态机 → 支付 → 出票 │ │ ⏰ 超时未支付 ──▶ 自动释放锁定的票,回到可售 │ └──────────────────────────────────────────────┘ ``` > 灵魂是最上面那层**虚拟等候室**:它的作用不是「让大家排队」,而是**把洪峰挡在核心系统之外,按系统真实能力把流量「整形」成平稳水流**。没有它,核心系统会在开售那一秒直接被冲垮。 ## 5. 组件职责 - **虚拟等候室 / 排队**:开售时所有人先进队列,按系统承载能力分批放行。*为什么需要*:这是抗洪峰的根本——保护核心系统不被瞬时流量打死(见决策 1)。 - **抢票 / 库存服务**:用**原子操作**扣减库存,绝不超卖。*为什么需要*:防超卖是底线,普通的「读 - 改 - 写」在高并发下必然超卖(见决策 2)。 - **座位锁定**:下单时临时占住座位,给用户支付时间。*为什么需要*:座位有「正在被某人买」的中间态。 - **超时释放**:回收「占着不付」的锁定。*为什么需要*:否则票被占死,既卖不出也退不回(见决策 3)。 - **订单状态机**:管「待支付 → 已支付 → 出票 / 取消」。*为什么需要*:出票关乎钱和库存,状态不能乱。 - **防刷风控**:识别机器人 / 黄牛。*为什么需要*:公平性面临专业对抗。 ## 6. 关键数据流 **场景一:开售抢票(从洪峰到出票)** ``` 1. 开售,百万人涌入 ──▶ 全部进【虚拟等候室】,拿排队号 2. 系统按「每秒能处理 N 个」的速率,分批放行 ──▶ 进入抢票服务 3. 抢票:对热门场次库存做【原子扣减】 扣减成功 ──▶ 锁定座位,生成订单(待支付,给 10 分钟) 库存为 0 ──▶ 返回「已售罄」 4. 用户支付 ──▶ 出票,库存正式售出 5. ⏰ 10 分钟没付 ──▶ 自动释放,这张票回到可售池 ``` **场景二:为什么不超卖(原子扣减)** ``` ✗ 错误:先查余量(读到 1)→ 两个请求都以为还有 → 都扣 → 卖出 2 张(超卖!) ✓ 正确:用【原子操作】「扣减并返回结果」,只有一个请求能把最后 1 张减成 0, 另一个原子操作返回「不足」 → 只卖出 1 张 ``` ## 7. 数据模型与存储选择 核心实体:`库存 / 座位(状态:可售 / 锁定 / 售出)`;`订单(状态机)`;`排队令牌`。 | 数据 | 存储类型 | 为什么 | |---|---|---| | 热门场次库存 | 内存级 KV(原子操作) | 写竞争极热,数据库行锁扛不住瞬时洪峰 | | 座位图 / 状态 | 内存 + 关系型落账 | 实时性要求高,最终要持久化 | | 订单 | 关系型(强一致) | 关乎钱和出票,要事务 | | 排队令牌 | 内存级 KV | 高频、易失 | > 教学点:**「热点库存」是个超级写热点**——一个爆款场次的库存,瞬间被百万请求争抢同一行。把它放在内存级存储里做原子扣减,而不是让数据库的一行去扛,是抗住洪峰的关键。 ## 8. 关键架构决策与权衡 ⭐ **决策 1:虚拟等候室削峰(抗洪峰的根本)⭐** - 不削峰,让所有人直接打核心系统:开售瞬间核心系统被冲垮,谁也抢不到。 - 虚拟等候室:把人挡在门外排队,按系统能力放行。 - **取向**:大型抢票必上等候室。**把洪峰挡在门外,远比在门内拼命加机器有效**——这是应对尖刺流量的第一性原理。 **决策 2:库存扣减怎么保证不超卖?⭐** - 数据库「读余量 - 减余量 - 写回」:非原子,高并发下必然超卖。 - 数据库行锁 / 乐观锁:能防超卖,但热点行在洪峰下会成为瓶颈。 - 内存级原子扣减(单线程原子操作):又快又不超卖。 - **取向**:**并发不大用数据库行锁 / 乐观锁;热门洪峰场次用内存原子扣减**,再异步落库。详见第 12 节。 **决策 3:锁座的中间态——占而不付怎么办?⭐** - 不锁:用户选好座、还没付,被别人抢走 → 体验灾难。 - 锁了不设超时:占着不付,票永远卖不出去。 - 锁定 + 超时自动释放。 - **取向**:必做「锁定 + 超时释放」。**这是「临时持有稀缺资源」的通用模式**——和分布式锁、库存预占同理。 **决策 4:同步抢资格,异步走流程。** - 把「抢到资格(扣减库存)」做成同步、极快、原子;把后续「下单、支付」做成异步流程。 - **取向**:核心争抢点越轻越快越好,把重活儿挪到抢到之后再慢慢做。 ## 9. 规模化与瓶颈 - **第一个瓶颈:开售峰值。** → 破解:虚拟等候室削峰(根本手段)+ 前端静态化 + CDN。 - **第二个瓶颈:热点库存写竞争。** → 破解:热点库存放内存原子扣减;极热时**分段库存**(把 1000 张拆成 10 段各 100 张,分散竞争)。 - **第三个瓶颈:查余票的读放大。** → 破解:多级缓存(余票状态缓存,容忍秒级延迟)。 - **第四个瓶颈:排队系统自身要扛住所有人。** → 破解:等候室本身要做得极简、可大量水平扩。 ## 10. 安全与合规要点 - 🔴 **防机器人 / 脚本抢票**:验证码、设备指纹、风控、实名 + 限购,对抗黄牛——这是公平性的技术防线。 - **超卖防护**:原子扣减是安全问题(超卖 = 卖出不存在的资产)。 - **防刷接口**:开售前的探测、刷余票接口都要限流。 - **支付安全**:见 [支付系统模板](../payment-system/README.md)。 ## 11. 常见误区 / 反模式 - ❌ **开售让所有人直接打核心系统** → ✅ 虚拟等候室削峰,把洪峰挡在门外。 - ❌ **用「查余量 - 减余量」非原子扣减** → ✅ 原子操作 / 行锁,否则必超卖。 - ❌ **锁座不设超时** → ✅ 超时自动释放,别让票被占死。 - ❌ **热点库存硬塞普通数据库扛写** → ✅ 热点放内存原子扣减,必要时分段。 - ❌ **不防机器人** → ✅ 风控 + 实名 + 限购,守住公平。 ## 12. 演进路线:MVP → 成长期 → 成熟期(不同阶段怎么设置) | 阶段 | 规模量级 | 怎么设置(具体) | 此时该操心什么 | |---|---|---|---| | **MVP** | 小型活动报名 | 数据库**行锁 / 乐观锁**扣减库存即可,并发不大不必上重型方案 | 先保证不超卖,别过度设计 | | **成长期** | 中等抢购 | 热点库存放 **内存原子扣减**、锁座超时释放、限购、基础防刷、异步下单 | 热点写竞争、超卖、占座释放 | | **成熟期** | 大麦 / 12306 级 | **虚拟等候室**削峰、分段库存、多级缓存查余票、风控反黄牛、全链路异步化 | 峰值承载、公平对抗、稳定性 | ## 13. 可复用要点 - 💡 **「把洪峰挡在门外」比「在门内加机器」更根本。** 排队 / 等候室 / 限流,是应对瞬时尖刺流量的第一选择——这和 [消息队列削峰](../../tutorial/04-十大核心架构模式.md) 同源。 - 💡 **原子操作是防超卖的命根子**:任何「争抢有限资源」的场景(秒杀、抢券、抢座),都要用原子扣减,而非「读 - 改 - 写」。 - 💡 **「占用 + 超时释放」是处理「临时持有稀缺资源」的通用模式**:订座、库存预占、分布式锁,都是它。 - 💡 **把超热点数据放内存**:让一行数据库去扛百万并发写,是注定的瓶颈;内存级原子操作才扛得住。 ## 🎯 随堂检验 --- ## 参考原型与延伸阅读 > 本模板基于以下**公开工程资料**整理。抢票 / 票务系统的核心难点(虚拟等候室、削峰、座位锁定)在下面几篇里讲得很透。 **📖 工程文章 / 方案:** - [Queue-it: Virtual Waiting Room](https://www.queue-it.com/virtual-waiting-room) — 虚拟等候室的商业方案,FIFO + 随机化排队、按「每分钟放行量」控制出流,防峰值压垮与防刷。 - [AWS: 用 Queue-it 虚拟等候室应对峰值流量](https://aws.amazon.com/blogs/apn/how-to-manage-peak-traffic-on-aws-using-queue-its-virtual-waiting-room/) — AWS 官方博客,给出虚拟等候室的落地架构(队列状态存储、放行控制)。 - [InfoQ: SeatGeek 如何应对高需求开售](https://www.infoq.com/presentations/ticketing-system-virtual-waiting-room/) — SeatGeek 工程分享,讲票务系统用虚拟等候室处理高需求开售(座位锁定 / 排队 / 限流)。 --- > 📌 一句话记住抢票系统:**它不是「一个卖票的网站」,而是「极端瞬时洪峰争抢稀缺资源的战场」——所有设计都在回答『怎么同时做到不超卖、不崩、还相对公平』:把洪峰挡在门外、用原子操作守住库存、给占座装上超时。**