大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
系统设计是什么
这一课你将能做到
- 用「澄清 → 估算 → 高层设计 → 深挖」四步法,独立拆解一道系统设计题
- 对「10 万日活的在线判题平台」完成 QPS、并发判题数、存储增长的数量级估算
- 区分功能需求与非功能需求,写出可以直接放进设计文档的假设清单
为什么要学
面试从二面开始,几乎必有一轮系统设计:「设计一个短链接服务」「设计一个在线判题系统」「本站要扛 10 万日活,架构怎么演进」。答得好坏直接决定 offer 档位。
工作中它同样每天都在发生:技术方案评审、容量规划、故障复盘,本质都是系统设计——判断「这套架构撑不撑得住、坏了会怎样、下一步往哪扩」。
人话:算法题是练拧螺丝的手艺,系统设计是练画建筑图纸的能力——楼盖几层、电梯装几部、消防通道留在哪、预算多少,全是在约束下做取舍。没有标准答案,只有「能不能自圆其说 + 每个选择有没有代价分析」。
先建立图像
本站(Spring Boot + MySQL + Redis + Nginx + Kafka 的在线判题/学习平台)全景骨架长这样:
浏览器 / App
│
CDN(静态资源、题目图片)
│
Nginx(负载均衡 + 限流)
│
Spring Boot × N(无状态 API)
│ │ │
Redis Kafka MySQL
缓存 判题队列 持久化
│
判题机 × N(Docker 沙箱执行代码)
核心动作只有一条主路径:提交代码 → API 校验入库 → 发 Kafka → 判题机消费执行 → 结果写回 → 用户看到 AC/WA。其余(排行榜、统计、通知)都是旁路。
接着是估算。记住一个推演链条:日活 → 每日操作数 → 平均 QPS → 峰值 QPS(×3~5)→ 并发数(Little's Law)。
| 指标 | 假设 | 推导 | 结果 |
|---|---|---|---|
| 日活 DAU | 10 万 | — | — |
| 浏览行为 | 人均 30 页/天 | 300 万/天 ÷ 86400s | 平均 35 QPS,峰值 ~175 QPS |
| 代码提交 | 人均 5 次/天 | 50 万/天 ÷ 86400s | 平均 6 QPS,峰值 ~30 QPS |
| 判题并发 | 每次判题占 CPU 10s | 30 QPS × 10s | ~300 个并发判题槽位 |
| 提交存储 | 2KB/条(代码+元数据) | 50 万 × 2KB | ~1GB/天,一年 ~365GB |
| 读写比 | 看题远多于提交 | — | 约 9:1 |
两个关键公式:
峰值 QPS ≈ 日操作总量 ÷ 86400 × 峰值系数(3~5)
并发数 = QPS × 平均耗时 (Little's Law)
第三行的判题并发是最有价值的一步:判题是 CPU 密集型且单次要跑 10 秒,30 QPS 看着不大,但 300 个并发执行槽说明判题机必须独立成池,这是本站和普通 CRUD 平台最大的架构差异。
跟着做
步骤 1:澄清需求(面试前 5~10 分钟)
拿到题不要画图,先问。针对判题平台,合格的澄清问题长这样:
1. 支持哪些语言?——影响沙箱镜像数量
2. 判题耗时预期?——用户能接受排队 30 秒还是 3 秒
3. 有没有比赛场景?——流量瞬间放大 10~50 倍,决定削峰设计
4. 防作弊要求?——决定代码是否需要查重、沙箱隔离级别
5. 一致性底线?——比赛计分能延迟多久、能不能错
每得到一个答案,立刻复述成假设:「好,那我假设 DAU 10 万、人均提交 5 次、读写比 9:1」——这是面试官判断你专业度的第一个信号。
步骤 2:容量估算
套用上面的推演链条,把结果写下来。10 万日活得出:峰值浏览 ~175 QPS、峰值提交 ~30 QPS、判题并发 ~300 槽、存储年增 ~400GB。
结论先行:单机 MySQL + 单台判题机扛不住 300 并发判题,但浏览流量其实不大——瓶颈第一眼就落在判题池,而不是很多人以为的数据库。
步骤 3:高层设计(画主路径)
提交 API:校验代码 → 写 MySQL(submission, status=PENDING) → 发 Kafka
判题机:消费 Kafka → 拉起 Docker 沙箱编译运行 → 比对输出 → 写回结果
查询 API:GET /submissions/{id} 轮询状态(或 WebSocket 推送)
每条箭头标注 QPS:API→MySQL 写 30 QPS,API→Kafka 30 msg/s,判题机消费能力必须 ≥ 30 QPS 才不积压。
步骤 4:深挖瓶颈
对图中每个组件问三个问题:热点在哪?单点是谁?挂了怎么办?
- 判题机:CPU 密集,与 API(IO 密集)分开扩容;沙箱要限时、限内存、禁网络
- Kafka:比赛开始瞬间 500 QPS 提交,靠队列削峰,前端展示「排队第 N 位」
- MySQL:提交表按 userId 分片留到数据架构课处理;浏览流量加 Redis 缓存
至此一份完整的四步法答卷就有了骨架。
讲清楚要点
- 系统设计 = 在约束下做可辩护的取舍:面试评分不看图多炫,看每个组件的选择能不能说出「为什么是它、代价是什么」。
- 非功能需求决定架构,功能需求决定模块:「判题 3 秒内出结果」这个 NFR 直接催生 Kafka + 判题池,比任何功能列表对架构的影响都大。
- 估算只要数量级(±1 个数量级即可),但必须会算:写不出精确数就声明假设,「假设读写比 9:1」比沉默强一百倍。
- 先主路径,再旁路:80% 流量走的「提交→判题→看结果」先画通,排行榜、通知等旁路留到深挖阶段,否则时间不够。
- 故障思维贯穿始终:每个组件挂掉时的行为(降级、排队、报错文案)是区分「背题」和「做过」的分水岭。
你可能会懵的地方
-
系统设计和画架构图是一回事吗? 不是。图只是产出物,设计是推理过程:为什么这里要队列、为什么那层要缓存。面试中推理断掉,图再漂亮也拿不到高分。
-
没用过 Kafka/Redis,能学系统设计吗? 能。系统设计学的是模式(削峰、缓存、分片),Kafka 只是削峰的一种实现。先懂模式,上手具体中间件只要几天。
-
面试官一直追问不会的细节怎么办? 顺着瓶颈往下拆一层即可。没人什么都懂,关键是展示方法:「这里可能积压,我的应对是……具体参数上线前要压测」。
-
系统设计题真的没有标准答案? 没有,但有错误答案:说不清取舍的过度设计、漏掉非功能需求的精致图画。评分维度通常是:假设合理性、估算、权衡、故障分析、沟通。
-
常用基准数字要背吗? 要,记住量级即可:内存随机读 ~100ns、Redis 一次 ~0.5ms、MySQL 主键查 ~1-5ms、SSD 随机读 ~100μs、机房内 RTT ~0.5ms、跨区域 ~30ms+。有这些,估算随口就来。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 上来就画全景架构图 | 没澄清需求,方向可能全错 | 先问 3~5 个澄清问题再动笔 |
| 把所有中间件都堆上图 | 过度设计,说不清为何需要 | 每个组件讲清它解决什么问题 |
| 只设计正常流程 | 故障才是深挖重点 | 每个组件过一遍「挂了会怎样」 |
| 估算精确到小数点 | 精确是假的,还浪费时间 | 数量级 + 展示口算过程 |
| 完全不谈成本 | 无法权衡,像理想主义 | 给出改动需要的机器/人力代价 |
卡点急救
- 拿到题不知道先说什么:背熟开场白——「我先确认几个需求和量级:用户量、读写比、延迟要求……」,问完即估算,永远不会冷场。
- 数字完全没有概念:把本文「先建立图像」的估算表抄进笔记本,用自己的产品重新推一遍,做三次就有手感。
- 画图画到一半发现前后矛盾(比如判题机消费速度低于生产速度):不要擦图重来,当场指出矛盾并修正——「我发现判题吞吐不够,这里需要加判题机或削峰」,这反而是加分动作。
面试常问
-
「设计一个短链接系统」怎么入手? 四步法:澄清读写比(典型 100:1,读远大于写)→ 估算(1 亿/年短链,62 进制 6 位够用)→ 高层图(发号器 + 重定向服务 + 缓存)→ 深挖(301 还是 302、热点 key、发号器单点)。
-
为什么要先做估算再设计? 估算决定架构档位:175 QPS 单机就够,1.7 万 QPS 必须缓存 + 多实例,17 万 QPS 要上 CDN 和边缘计算。跳过估算,要么过度设计要么设计不足。
-
你的这个设计最大的风险是什么? 这是考察故障思维。判题平台的标准回答:判题池被恶意代码打满(应对:沙箱限时限资源 + 队列优先级),以及比赛瞬间流量洪峰(应对:Kafka 削峰 + 前端排队提示)。
自测三问
- 10 万 DAU、人均 20 次提交/天、峰值系数取 5,峰值提交 QPS 是多少?
- Little's Law 是什么?判题单次 10 秒、峰值 30 QPS,需要多少并发判题槽?
- 各举两个功能需求和非功能需求的例子,并说明哪个对架构影响更大。
小结一句话
系统设计 = 澄清 → 估算 → 高层图 → 深挖瓶颈,每一步都要能说出「为什么」和「代价是什么」。
延伸
- 《System Design Interview》Alex Xu(Vol.1/2):面试导向的经典,四步法源自此
- 《Designing Data-Intensive Applications》:数据系统设计的圣经,本 track 后续课程的多处推导出自它
- Latency Numbers Every Programmer Should Know:估算用的基准数字表
- 下一课:性能与扩展基础——延迟、吞吐、并发到底怎么区分,垂直与水平扩展怎么选。