系统是被打出来的 · 系统设计实战手册
PART 1 · 单机时代 · 第 1 章

第一次卖票
一个单体的诞生

这一章没有故障,只有 deadline。你会画出全书的第一张架构图——一张简单到有点丢人的图。后面十五章都在改它,而它每一次被改,都是因为挨了打。

01 · 1 值班现场周一上午,白板上只有一个日期

你是「秒抢」的第 3 号员工,后端工程师。公司总共三个人:老陈(CTO,也是你面试官)、一个前端、你。办公室是租来的一间十二平米的房子,白板上没有架构图,只有一个日期——两周后的周五。那天有一场小型音乐节要开票,主办方答应给「秒抢」独家。这是公司的第一笔生意,也可能是最后一笔。

老陈把需求写成了四行字,就是全部产品文档:

WHITEBOARD · 距离开票 14 天
[MUST]  用户能看到场次、选座、下单
[MUST]  能付钱(接第三方支付)
[MUST]  运营能上架场次、改票价
[MUST]  用户能注册登录、看自己的订单
──────────────────────────────────────
你的估算(老实版):
四件事全做完,需要约 4 周。你只有 2 周。

这是全书唯一一次,危机不是「系统坏了」,而是「系统还不存在」。但它和后面十五章遵守同一条规律:在几乎每一个增长阶段,你的资源都小于需求;工程的本质是决定先付哪一笔账。

团队规模
3 人
后端只有你
已有用户
0
还没上线
剩余工期
14 天
● 不可延期
诚实估算
28 天
▲ 缺口 2 倍
决策点 D1

四件 MUST,两周只做得完两件半。你砍什么?

A 是对的,理由只有一条:它保住了完整的主链路。「看场次 → 下单 → 付钱 → 拿到票」这条链路上任何一环缺失,产品就不是「功能少」,而是不成立。选座是体验,随机分配也能卖票;运营后台是效率,只有一场演出时你手写四条 INSERT 完全够用。砍功能,不砍链路——这是 MVP 唯一的裁剪准则。

B 砍掉了链路本身。线下转账听起来"也能收到钱",但它把一个自动化系统降级成了一门手工生意:对账要人、退款要人、并发售卖时你无法知道哪张票已经卖出去了。更要命的是它掩盖了库存问题——而库存正是这门生意最难的地方(第 10 章会把这个问题正面拆开)。

C 可行,但它埋了一颗有名字的雷。匿名下单确实能跑通链路,代价是订单没有稳定的归属主体:用户换个手机号就找不回订单,客服无法验证身份,后面接入退款和实名购票时要做一次数据迁移。如果只做一场演出,C 是可以接受的;一旦你打算做第二场,这笔账就会来收。

D 是最诱人的错误答案,因为它听起来最努力。四个八成不等于八成的产品,等于零个能用的链路——用户能选座却付不了钱,和网站没上线没有区别。工期不够时,正确动作是缩小范围,不是降低每件事的完成度。

01 · 2 原理一次请求的一生,和它带来的账单

在写第一行代码前,你需要回答两个问题:一次请求会经过哪些环节,以及这些环节要准备多大的量。前者决定架构长什么样,后者决定它要多贵。

一次请求经过什么

用户在手机上点「立即购买」,到他看见「下单成功」,中间发生的事按顺序是:

  1. DNS 解析:把 miaoqiang.com 变成一个 IP。结果会被缓存,通常不是瓶颈,但它是你改不动的第一环。
  2. 建立连接:TCP 三次握手 + TLS 握手。跨省一个来回就是几十毫秒,握手要好几个来回——这是为什么连接复用(Keep-Alive)不是优化,是基本盘。
  3. 反向代理(Nginx):接住连接、终止 TLS、分发静态文件、把动态请求转给应用。它也是你未来放限流的地方(第 6 章)。
  4. 应用进程:路由、参数校验、鉴权、业务逻辑。这是全链路上你改动成本最低的一层,所以后面十五章的多数动作都发生在这里。
  5. 数据库(MySQL):连接池取连接 → 执行 SQL → 事务提交 → 归还连接。这是全链路上最慢、最贵、最难扩的一环,本书大半篇幅都在想办法少打扰它。
  6. 原路返回:结果序列化、经 Nginx、经网络、回到用户屏幕。
记住这六步的顺序,因为排障是沿着它倒着走的。用户说「慢」,你的任务是判断慢在第几步。第 14 章会告诉你怎么用三种工具分别看清这六步。

容量估算:三步法

「你的系统要扛多少?」是面试和真实工作里都绕不开的问题。它不需要精确,需要的是量级正确。三步:

日请求量 = DAU × 人均请求数
平均 QPS = 日请求量 ÷ 86,400
峰值 QPS = 平均 QPS × 峰值倍数

老陈没有给你数字,只给了三句业务描述:「秒抢」首月要撑得住一场和这次差不多量级的独家开票;这场演出在售 5,000 张;他的原话是「先把量级估对,机器我批」。

交卷 E1

只凭这三句话,把「秒抢」首月的容量估出来

要交什么:① DAU 区间 ② 人均请求数(次/天)区间 ③ 日常峰值倍数区间 ④ 单次响应大小区间 ⑤ 平均响应时间(RT)区间——五个数都要给上下界,不要给单点值;⑥ 这五个假设里你最不确定的是哪一个,为什么;⑦ 再单独估一次开票瞬间的尖峰 QPS——这需要你自己补三个题面没给的假设:蹲点人数、抢购窗口(秒)、人均瞬时请求数,把这三个也写出来;⑧ 用你估出的尖峰 QPS(不是日常峰值——决定你要买多少机器的是尖峰),除以一个你自己假设的「单机安全吞吐」,给出机器数区间。把这个吞吐值标成假设:本章 ·6 复盘才会告诉你它的来处,现在你只能先拍一个区间。

写不出上下界、只写得出一个「拍脑袋数字」也要写——那本身就是有用的诊断。这五个数没有标准答案,「秒抢」的取值只是一份参考解,不是唯一解。答案不会被保存,刷新即失。

你还没写。这一页是这类练习专门检验你的独立输出,先写下来再看——再点一次仍会展开。

基准答案 · 「秒抢」的取值

先说清楚这份参考解怎么用:下面每个数「秒抢」都只取了一个点值,因为它要拿来往下算;而你交的应该是区间——两者不冲突,正确的做法是先给区间,再在区间里挑一个点值往下推,并写明挑的理由。例如 DAU 合理区间 1–5 万,「秒抢」取 2 万(早期获客保守估计);人均请求 20–50 次,取 30;日常峰值倍数 6–15(同类电商大促经验区间),取 10(中间偏保守)。如果你的区间把「秒抢」的取值包含在内,这一项就算过。

按这组取值往下推:

DAU
20,000
首月目标
人均请求
30
次 / 天
日请求量
60 万
次 / 天
平均 QPS
≈ 7
600,000 ÷ 86,400
日常峰值
≈ 70
×10 倍

7 QPS。日常峰值 70 QPS。一台四核机器打着哈欠就能接住。如果故事到此为止,这本书就只有一章。

但「秒抢」不是普通网站。它的流量不是一条平缓的曲线,而是一根针。把开票那一刻单独算一次:

在售票数
5,000
张
蹲点人数
50,000
人(10 倍超卖需求)
抢购窗口
10
秒
人均请求
3
次(刷新 + 下单)
瞬时 QPS
15,000
▲ 平均值的约 2,000 倍

50,000 × 3 = 150,000 次请求,压在 10 秒里,就是 15,000 QPS。它和你算出来的平均值 7 QPS 之间差了约 2,000 倍。

这个 2,000 倍是本书的起点。按平均值设计的系统,会被峰值杀死;按峰值堆机器的公司,会被账单杀死。整本书讲的就是怎么在这两者之间活下来——而不是二选一。

另外两笔账:存储与带宽

容量估算不只有 QPS。同样的三步法换个乘数就能用——响应大小和 RT 就是①②③之外你要交的另外两个数:

估什么怎么算「秒抢」首年它告诉你什么
存储 单行字节 × 日增行数 × 天数 订单行约 500 B,日均约 2,000 单 → 每天约 1 MB,一年约 365 MB 订单表一年不到 1 GB。单机 MySQL 能撑很久——所以分库分表不是第 2 章的事,是第 8 章的事
带宽 单次响应字节 × QPS 详情页首屏约 300 KB。日常峰值 70 QPS → 约 21 MB/s(约 168 Mbps) 日常可控。但开票瞬间 15,000 QPS × 300 KB ≈ 4.5 GB/s——这个数字不可能由你的机房出,只能由 CDN 出(第 12 章)
连接 并发数 ≈ QPS × 平均响应时间 70 QPS × 0.2s ≈ 14 个并发;15,000 QPS × 0.2s ≈ 3,000 个并发 这条式子(Little's Law)是第 2 章的主角,也是你第一次宕机的死因

从 QPS 到机器数:别停在 QPS

15,000 QPS 本身不能拿去跟老陈要机器——你要的是机器数区间。此刻你还没做过真实压测,只能先给一个假设区间:单机实测安全吞吐约 400–500 QPS。这个区间不是拍的:本章 ·6 复盘会给你这个数的来处——上线那夜 23:48 你会打一轮压测,200 QPS 时 P99 180ms、CPU 只到 40%,说明离饱和还有余量,线性外推到 CPU 打满大约是 400–500 QPS。15,000 ÷ 500 ≈ 30 台,15,000 ÷ 400 ≈ 38 台,区间取 30–38 台,并留出压测误差的富余,报给老陈的数字可以直接说「30 到 40 台,最终以压测收敛」。到了 ·6 你会看到,次日真实开票只跑到约 900 QPS、远低于 15,000——那不是估算错了,是这次入口只放了一个链接、人流被自然摊平;预算是按「被打满」的那种日子备的,不是按「运气好」的那种日子备的。

① 哪个假设最敏感

五个数在公式里都是线性放大器,误差 1 倍、结果就错 1 倍——单看数学,谁都一样敏感。真正拉开差距的是你能把误差压到多窄:DAU 是老陈拍的业务目标,人均请求有同类产品的经验值兜底,两者一般能收敛到 ±50% 以内;蹲点人数相对在售票量的倍数(这里假设 10 倍超卖需求)却是纯粹的猜测——没开过票,你不知道会不会来 5 倍人,也不知道会不会来 30 倍人。同样是「差一倍」,DAU 差一倍是没做好市场调研,这个倍数差一倍是正常。它被低估到 5 倍,瞬时 QPS 腰斩到约 7,500;被低估到 20 倍,瞬时 QPS 翻倍到约 30,000——机器数区间从「30–38 台」摆到「15–19 台」或「60–75 台」。这就是它最不确定的原因:不是数学上更敏感,是取值范围本身没有底。

② 下一步压测什么

两件事按顺序做:先在生产同规格机器上把单接口打到 P99 明显劣化的拐点,把「单机实测安全吞吐 400–500 QPS」这个假设换成真实数字——本章 ·6 复盘那次 23:48 的压测就是这一步的雏形,只是强度不够、没打到拐点,真要收敛这个数得往上继续加压。再找一场更小的演出提前开票,实测真实的蹲点人数/在售票量比例,把「10 倍超卖需求」这个最敏感的假设换成有数据支撑的区间。两件事换掉的分别是分母(单机吞吐)和分子(真实峰值 QPS)里误差最大的那个。

自评:你的答案里有没有这几样

  • 五个数每个都有上下界,不是单点值——上下界差太小(比如 ±10%)说明你在假装精确,不是在估算。
  • 指出了哪个假设最敏感,并且说出了「敏感」的理由,而不是随口挑一个。
  • 算出了日常峰值 QPS 和一个明显更极端的尖峰场景 QPS,并且写出了尖峰所需的那三个额外假设(蹲点人数 / 窗口 / 人均瞬时请求)——如果只算了日常峰值,你会漏掉这一章真正的教训。
  • 用尖峰 QPS(而不是日常峰值)折出了机器数区间,并把「单机安全吞吐」明确标成了假设。
  • 提出了具体的下一步验证动作(压测什么、怎么压),而不是「多测试一下」这种无法执行的话。

无论你算出的具体倍数是多少,这一章要教的规律都成立:日常均值和真正的抢购尖峰之间,差的不是百分之几十,而是数量级——这也是后面十五章要处理的核心矛盾。三笔账算完,结论出奇地一致:日常流量根本不需要复杂架构,而峰值流量再复杂的架构也未必扛得住。所以第一版就该简单——简单到你能在两周内写完、能在出事时看懂。

01 · 3 基线架构图全书的第 0 帧

两周后上线的东西长这样。把它记住,因为接下来的每一章都是在这张图上加东西——而每一次加东西,都是因为它被打疼了。

「秒抢」v1.0 · 上线当天的全部架构
浏览器
手机 H5
→
Nginx
反向代理
静态文件
TLS 终止
→
应用(单体)
一个进程
路由 / 业务 / 定时任务
全都在里面
→
MySQL
一台
无从库
连接池 20
没有的东西:缓存 · 消息队列 · 从库 · 网关 · 熔断 · 限流 · 监控大盘 · 第二台机器
有的东西:一台 4 核 8G 云服务器,Nginx 和应用和 MySQL 挤在上面

这张图会让任何一个做过大系统的人皱眉。但在两周工期、3 个人、0 用户的条件下,它是对的。理由是三条:

  1. 它能在两周内写完。没有跨进程调用,没有分布式事务,没有服务发现——一个函数直接调另一个函数。这是单体真正的优势,而不是「简单」这种模糊的说法。
  2. 它出事时你能看懂。一个进程、一份日志、一个数据库。凌晨三点你不需要在四个服务之间对时间戳(那种日子在第 13 章等着你)。
  3. 它的每一处不足都是可定位的。缓存缺失、单点数据库、没有限流……这些都不是隐患,是写在图上的待办事项。真正危险的架构不是简陋,是没人说得清它长什么样。

接下来会发生什么,这张图已经写好了剧本:

这张图的哪个位置会在第几章挨打被打成什么样
应用 → MySQL 的连接池第 2 章连接池打满,全站 502
单台 MySQL第 3 章加从库,然后被复制延迟坑
「没有缓存」这一栏第 4–5 章加了缓存,然后被缓存杀死
Nginx 这一层第 6 章被迫学会说「不」
「一个进程」第 13 章拆成几十个,然后为此付账
整张图第 16 章你会亲手把它打一遍

第 16 章的收尾,我们会把这张图和它的最终形态并排放在一起。到那时你会发现:中间那十四章加进去的每一个方框,都对应一次真实的疼痛——没有一个是因为「架构应该这样」而加上去的。

01 · 5 权衡没有正确答案,只有代价

图画完了,还剩一个问题:用什么写。老陈把选择权给了你,附带一句「别让我三个月后招不到人」。三个候选都能在两周内交付,每一个都是对的,每一个都有账单。

决策点 D2

3 人团队、2 周工期、后端只有你一个。技术栈怎么选?

A · 成熟全家桶:两周工期下开发速度最快,管理后台、鉴权、数据库迁移这些"必须有但不产生差异化"的东西开箱即用。招人也最容易——市面上会的人多。账单:框架替你做的决定,出问题时你也得替它背;性能调优会撞上你没写过的那一层(连接池默认值、ORM 生成的 SQL、自带的会话机制)。第 2 章那次宕机,起因就藏在一个你没看过的默认值里。适合业务形态清晰、团队要跑得快的早期阶段。

B · 轻量自组:每一层都是你选的,因此每一层你都知道它的边界在哪;性能上限高,依赖少,升级时不会被框架的大版本绑架。账单:两周里你要花掉相当一部分时间造「所有人都造过」的轮子(后台、鉴权、迁移工具),而这些轮子没有一个能变成「秒抢」的竞争力;新人上手成本高,知识全在你脑子里——这在 3 人公司是最大的单点。适合团队里有明确的技术主张、且工期不是最紧约束时。

C · 云托管优先:托管数据库省掉备份、主从、监控的搭建,3 人团队里没有专职运维,这笔省下来的时间是真金白银。账单:单位成本明显更高,且成本随流量非线性上涨;你对底层的可观测性和调参能力被平台限制住——等你需要看慢查询日志、调复制参数、做故障演练时(第 3、14、16 章),能不能做取决于平台给不给。适合流量可预期、团队极小、且愿意用钱换时间的阶段。

「秒抢」的选择:A + C 的组合——用成熟全家桶写应用,数据库用云厂商的托管实例。理由很实际:两周工期下,A 省的是"写代码的时间",C 省的是"不写代码但必须做的时间",而 B 省的东西(可控性)在 0 用户阶段还兑现不了价值。代价你也认了:框架的默认值你没有逐个看过。这笔账在第 2 章就会来收——两周后的深夜,连接池会用一次全站 502 提醒你,你没读过的默认值,也是你的架构决策。

01 · 6 复盘上线那一夜

两周后的周四深夜,第一版上线。没有告警,没有大盘,只有你和老陈盯着一个 tail -f 的窗口。留意这条时间线的顺序——它是本书所有复盘的第一条,也是最平静的一条:

23:10

部署。手动 git pull + 重启进程。整个过程 40 秒,期间站点不可用——但此刻还没有用户,代价为零。这个"代价为零"的假设,会在第 13 章过期。

23:25

自测下单。你用自己的手机号买了第一张票,订单号 O-000001。支付回调花了 1.2 秒返回,比预期慢,但能用。你在便签上记了一行:「支付回调超时怎么办?——待定」。

23:48

压了一轮。用一个命令行工具打了 200 QPS,持续两分钟。P99 是 180ms,CPU 40%,没有报错。你得出结论「够用了」——这个结论在流量形状变化前一直成立。

次日 10:00

开票。5,000 张票在 6 分钟内卖完。峰值大约 900 QPS,比你压测的高,但没到你算的 15,000——因为主办方只放了一个入口链接,人是慢慢来的。你侥幸赢了第一场。

壹
先算量级,再画架构。三步法算出来的 7 QPS 和 15,000 QPS,指向的是两种完全不同的系统。跳过估算直接选型,你不是在做架构,是在赌。而估算只要量级对就够了——差 3 倍无所谓,差 2,000 倍会死人。
貳
MVP 砍功能,不砍链路。判断一个需求能不能砍,只问一句:砍掉之后,「看 → 下单 → 付钱 → 拿到票」这条链路还完整吗?完整就砍,不完整就留。四个八成的功能,价值低于两个完整的环节。
叁
你没读过的默认值,也是你的架构决策。框架替你设定的连接池大小、超时时间、线程数,在流量小的时候一个都不会出声。它们会在你第一次真正需要它们的那个深夜,一起出声。第 2 章就是那个深夜。

01 · 7 自测你的估算肌肉

Q1 · 看形状诊断

两个产品的日流量曲线:X 的形状是 ▃▄▅▆▅▄▃(白天缓慢起伏、夜间回落);Y 的形状是 ▁▁▁█▁▁▁(全天极低,某个固定时刻一根垂直尖峰)。同样是日均 60 万请求,关于它们的容量规划,哪个判断最准确?

C。容量由峰值决定,不由总量决定——这是三步法里「峰值倍数」那一项存在的全部理由。X 的峰值倍数大概两三倍,Y 可能是几百上千倍。A 是最诱人的错误答案,因为「日均相同」听起来像是同一个规模的系统;但服务器接住的是同时到达的请求,不是一天的总和。B 把「持续有负载」当成风险,实际上平稳负载恰恰是最好规划的一种。D 的 1.5 倍对 X 都嫌紧,对 Y 是灾难。「秒抢」正是 Y。

Q2 · 三步法算术

「秒抢」第二年 DAU 涨到 50 万,人均每天 40 次请求,日常峰值倍数按 8 估。日常峰值 QPS 大约是多少?

B。日请求量 = 500,000 × 40 = 2,000 万;平均 QPS = 20,000,000 ÷ 86,400 ≈ 231;峰值 = 231 × 8 ≈ 1,850。A 是最容易掉进去的坑——它算对了平均 QPS 却忘了乘峰值倍数,而这一步正是三步法里唯一能救命的一步。C 是把 86,400 秒错记成 3,600 的结果——只除了一小时:20,000,000 ÷ 3,600 ≈ 5,556,再乘 8 约 44,000。D 则是完全没除时间:直接拿日请求量 2,000 万乘 8,得到 1.6 亿。记住这个数量级感觉:百万级 DAU 对应的日常峰值通常是几千 QPS,不是几十万。

Q3 · 并发估算

某接口平均响应时间 200ms,日常峰值 1,850 QPS。峰值时刻大约需要多少个并发处理槽位(连接 / 线程)?

B。并发数 ≈ QPS × 平均响应时间(Little's Law):1,850 × 0.2s ≈ 370。C 很诱人,因为「每秒 1,850 个请求」听起来就该有 1,850 个槽位;但一个槽位在 1 秒内可以服务 5 个 200ms 的请求,所以需要的是 1,850 ÷ 5 = 370。A 把单位搞混了(秒和毫秒)。D 听起来最"严谨",实际是拒绝估算——核数影响的是你能开多少槽位,不影响你需要多少槽位。这两个数字的关系,就是第 2 章的全部内容。

Q4 · MVP 裁剪

团队工期不足,四个需求必须砍掉两个。下面哪条是最可靠的裁剪准则?

B。MVP 的价值在于「有一条完整的路能走通」,而不是「做了很多东西」。A 是最专业的错误答案——它优化的是"省下多少工时",但如果那个最费时的需求恰好是支付,砍掉它省下的时间一文不值,因为剩下的东西卖不了票。C 的直觉在多数产品里成立,在这里却危险:运营后台确实可以砍(你手写 SQL 顶两周),但「用户看不见」不是判据,链路是否完整才是。D 见 D1 选项 D:四个六成等于零个可用。

🎯 面试映射 · 本章直接覆盖的高频追问

「估算一下这个系统需要多少台服务器 / 多大容量。」答题骨架:先要 DAU 和人均请求数(没有就自己假设并说出来)→ 日请求量 → 平均 QPS → 乘峰值倍数(说明为什么这个业务取这个倍数)→ 再用 并发 ≈ QPS × RT 折成机器数 → 最后单独说一句存储和带宽。反直觉加分点:主动指出峰值形状比总量重要,并问一句「这个业务的流量是平缓型还是尖峰型」。
「讲一下从用户点击到看到结果,中间经过了什么。」答题骨架:DNS → 建连(TCP + TLS 握手,强调来回次数)→ 反向代理 → 应用(路由/鉴权/业务)→ 数据库(连接池 → SQL → 事务)→ 原路返回。每一环补一句「这里可能慢在哪」,并点出数据库通常是最贵的一环。
「一开始就该上微服务吗?」答题骨架:不按偏好答,按信号答 → 团队规模与发布冲突频率、变更频率差异、故障隔离需求 → 说明单体在小团队下的真实优势是"函数调用而非网络调用" → 给出拆分的触发条件而不是时间表。可以主动说:拆分不降低复杂度,只是把它从代码搬到网络上(第 13 章会展开)。