大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
综合练习
这一课你将能做到
- 独立完成一份完整系统设计答卷:「判题平台支撑 10 万日活」从澄清到深挖 45 分钟闭环
- 产出三阶段演进路线(现状 → 10× → 100×),每阶段写清改动、预期容量、代价与风险
- 用面试官的评分表给自己的设计打分,并完成一轮同伴互审
为什么要学
前 11 课的每个概念(估算、CAP、负载均衡、缓存、消息、分片、网关、云模式、可靠性、反模式)都是零件,这道综合题是把零件装成整机——面试就是这么考的,技术方案评审也是这么写的。
人话:学零件时你是修理工,这道题让你当总工程师。总工程师的标志不是会用所有零件,而是知道每一步的代价、每条路径的故障行为、每个假设的验证方法。
先建立图像
题目(模拟面试原题):
背景:本站在线判题/学习平台,现状单体 Spring Boot 3.5 + MySQL 8,
2 个 API 实例 + 1 台判题机,无 Redis、无 Kafka、Nginx 单层。
现状:峰值 800 QPS,P99 450ms,判题排队高峰 5 分钟。
目标:推广活动后 10 万日活;比赛场景瞬时提交 500 QPS 持续 1 分钟;
SLA 99.9%,判题结果查询 P99 < 300ms,允许排行榜分钟级延迟。
第一反应必须是澄清与估算(第 1 课四步法):
| 项 | 假设 | 推导 | 结论 |
|---|---|---|---|
| 浏览流量 | 人均 30 页/天 | 300 万/天 | 峰值 ~175 QPS |
| 提交流量 | 人均 5 次/天 | 50 万/天 | 峰值 ~30 QPS,比赛瞬时 500 QPS |
| 判题并发 | 10s/次 | 30 QPS × 10s | 300 并发槽,判题机 ≥ 30 台核 |
| 读写比 | 看题为主 | — | 9:1,读路径是优化主战场 |
| 存储 | 2KB/条 | 50 万/天 | 1GB/天,一年 365GB → 100 天单表触顶 |
关键结论先行:浏览量不大(单机都能扛),真正的三座山是判题吞吐、比赛洪峰、提交表增长。设计要对着这三座山打。
跟着做
步骤 1:画目标架构图(每条线标注流量)
浏览器
│
CDN(静态资源 + 题目描述)
│
Nginx L7(限流 + 路由 + 探活) ← 第 4 课
│
┌─────────────┼──────────────┐
│ │ │
题目服务×2 提交API×3 查询API×3 ← 无状态,水平扩展
│ │ │
│ Kafka judge-task │ ← 第 6 课,24 分区
│ │ │
│ 判题机池 ×N(沙箱) │ ← CPU 密集独立池
│ │ │
└──── Redis ──┴──── MySQL ───┘
题目缓存 主从 + 提交表分片 ← 第 5/7 课
排行榜ZSet
标流量:CDN 承接 70% 浏览 → Nginx 进 175 QPS → 提交 API 30 QPS 写库+发消息 → 判题机池 ≥100 QPS 消费能力 → 查询 API 读 Redis 95% 命中、5% 回源从库。
步骤 2:逐组件写清「它解决什么 + 挂了怎么办」
每个组件两句话过审,这是评审与面试的通用纪律:
Redis:解决读路径 95% 命中(题目 JSON + 排行榜 ZSet);
挂了降级直查从库 + 读接口限流,排行榜退回 MySQL 聚合页。 ← 第 5/10 课
Kafka:解决比赛 500 QPS 洪峰削峰,积压 2.4 万条 ≈ 排队 4 分钟可见;
挂了提交降级为「写库 PENDING + 定时扫描」,提交不丢。 ← 第 6 课
判题池:解决 300 并发 CPU 槽,与 API 部署隔离(舱壁思想到物理层);
沙箱限时限内存禁网络,防恶意代码打满池。 ← 第 9 课
MySQL:读写分离 + 提交表按 userId 分 4 片 + 90 天冷热归档;
主库挂半同步切换 RPO=0、RTO 60s。 ← 第 7/10 课
步骤 3:写出三阶段演进(每阶段可独立交付)
| 阶段 | 改动 | 预期容量 | 代价与风险 |
|---|---|---|---|
| Phase 1 稳基线 | 题目读路径加 Redis cache-aside(TTL 抖动+空值防穿透);提交接口接入 Kafka + 判题机独立池;Tomcat/HikariCP 超时与池参数校准 | ~3000 QPS;比赛洪峰排队可控 | 缓存一致性要写双删;Kafka 运维新增 |
| Phase 2 拆读写 | 读走从库 + 读己之写粘滞;Nginx 限流 + 网关统一鉴权;读/写舱壁隔离;放大倍数监控上线 | ~6000 QPS;P99 < 200ms | 主从延迟产品文案;限流阈值需压测校准 |
| Phase 3 抗增长 | 提交表分 4 片 + 冷热归档;排行榜 CQRS 对账;多 AZ 部署 + 健康检查三层 + 月度故障演练 | 10000+ QPS;SLA 99.9% 可承诺 | 分片键选定后迁移成本高(双写切换);演练要排期 |
顺序原则(第 2 课):每阶段从便宜到贵、可独立回退,Phase 1 就能扛住推广活动——不要被「一步到位」诱惑。
步骤 4:用评分表自审(面试官视角)
□ 需求澄清:有没有把 NFR(延迟/一致性/SLA)确认成假设? ← 第 1 课
□ 估算:QPS/并发/存储是否给了数量级和推导过程? ← 第 1/2 课
□ 一致性:提交状态强一致、排行榜最终一致,标注了吗? ← 第 3 课
□ 扩展路径:无状态、缓存、读写分离的顺序对吗? ← 第 2/4 课
□ 异步设计:削峰算过账吗?消费端幂等怎么保证? ← 第 6 课
□ 数据架构:分片键为什么是 userId?跨片查询怎么兜? ← 第 7 课
□ 故障行为:每个组件「挂了怎么办」都能一句话回答吗? ← 第 10 课
□ 反模式自查:放大倍数、重试四件套、无缓存读路径排除了吗? ← 第 11 课
□ 权衡与代价:每步改动说了人机成本吗?
八项里能主动讲出六项,面试设计关就稳了。
步骤 5:同伴互审的两个问题
互审只问两个问题(模拟面试官追问):
- 「这套架构第一个崩的是什么?」——压力逐级放大时的观察顺序:判题池 CPU 100% → Kafka lag 增长 → Redis 命中率下降(热点题目变多)→ MySQL 连接耗尽。答不出顺序说明容量数字是拍脑袋的。
- 「比赛开始 10 秒后用户看到什么?」——标准答案:提交成功返回「排队第 N 位」(N 来自 lag),状态轮询 P99 仍 < 300ms(读路径与写路径已隔离),排行榜延迟标注「分钟级」。答「页面卡住」就是读写没有隔离。
讲清楚要点
- 先结论后细节:答卷开头一句话给结论(「瓶颈在判题池与洪峰,读路径单机即可起步」),再展开推导——面试官 45 分钟里最先想听的是你的判断力。
- 每个数字必须可追溯:DAU→操作→QPS→并发→资源,链条断了就承认是假设并标注验证方式(压测),不要让任何数字裸奔。
- 架构的每一个组件都要「双句过审」:一句「解决什么问题」,一句「挂了怎么办」。两句都答不上来的组件,从图上删掉。
- 演进阶段以「独立可交付」为界:每个 Phase 单独上线、单独回滚、单独产生收益。评审通过的是路线图,不是终态图。
- 一致性标注是暗线:整份答卷里,提交状态(强)、排行榜(最终)、CDN 题目描述(弱)三条线的取舍贯穿读写两侧——面试官会沿这条线追问。
你可能会懵的地方
-
45 分钟答不完这么多内容怎么办? 优先级就是答案:澄清估算 10 分钟 → 主路径图 + 三座山 15 分钟 → 挑面试官最感兴趣的组件深挖 15 分钟(判题池/缓存/分片三选一)→ 收尾故障行为与演进 5 分钟。深挖点跟着面试官的眼神走,而不是按自己的提纲硬讲。
-
设计里没有微服务、没有 K8s,会不会显得太简单? 不会,反而加分。10 万日活的正确形态就是「单体多实例 + 缓存 + 消息 + 分库预备」,把微服务塞进这份需求是过度设计的典型信号(第 8 课的判断框架自己说了「没有组织痛点不拆」)。能论证「不做什么」比堆组件高级。
-
面试官对我的分片键提出质疑(比如「按 create_time 范围分片更好」)怎么办? 不要立刻认怂,也不要硬顶:先复述对方的场景(范围分片对时间范围查询友好),再给出本站高频查询是 userId 维度的事实,最后说「若运营统计上升为主要负载,可以为其建按时间的异构读模型而不是改分片键」。这展示的是权衡能力而非立场。
-
如果被问到完全没准备的方向(比如国际化部署)怎么办? 诚实 + 方法迁移:「这块我没实操过,按现有框架推:判题沙箱涉及代码合规不能出境,所以要按地域做 deployment stamp,每 stamp 独立数据、全局流量按地域路由——具体合规细节我会上线前专门确认。」承认边界 + 用已有模式推理,比胡编强得多。
-
这份设计离「真实生产」还差什么? 三样:演练验证(月度故障演练证明 RTO)、成本核算(每 Phase 的月账单)、灰度策略(切流比例与回退按钮)。设计文档的最后一章永远是「怎么验证我说的这些都是真的」。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 开口就画全家桶(K8s+微服务+多机房) | 过度设计,说不清每个组件的必要性 | 对着三座山逐个上组件,双句过审 |
| 只画终态不画路径 | 评审无法排期,面试显得没做过 | 三阶段演进,每阶段独立交付 |
| 数字全是「大概几万」 | 不可信,暴露没做过估算 | 链条推导 + 声明假设 + 标注压测验证 |
| 只讲正常路径 | 故障才是深挖主战场 | 每组件一句「挂了怎么办」 |
| 深挖被质疑时慌乱改口 | 失去判断力的信号 | 复述对方场景→给数据→给折中方案 |
卡点急救
- 一动手就把估算忘了:把「DAU→操作→QPS→并发→资源」五步写在草稿纸顶上,画图前必须先填完这行——机械纪律比灵感可靠。
- 图越画越乱:分层重画——客户端层/入口层/服务层/数据层/异步层五条泳道,每个组件只出现在自己泳道里;对话中临时讨论的组件(如 ES)画在旁边虚线,标注「待评估」。
- 收尾时间不够:提前准备「电梯总结」——30 秒讲完主路径 + 三座山 + 阶段划分,被叫停时用这段收尾。永远让面试官听到完整闭环。
面试常问
-
用 45 分钟设计这个判题平台,你的整体节奏? 开场直接说节奏:「我计划 10 分钟澄清估算、15 分钟主路径、15 分钟按你感兴趣的方向深挖、5 分钟故障与演进。」然后严格执行。主动管理时间结构本身就是系统设计能力的展示。
-
你的设计里最可能先崩的组件是什么?为什么? 给出放大顺序并说明监测点:判题池(CPU 先顶,看沙箱并发数)→ Kafka lag(洪峰时最先涨的指标)→ Redis 命中率(热点扩容时降)→ MySQL(分片前的连接数)。同时说明每个监测点对应的告警阈值——把「会崩」讲成「可观测的何时崩」。
-
如果要支撑 100 万日活,这份设计哪里最先不成立? 考演化思维:提交表分 4 片不够(10× 数据 → 8~16 片 + 一致性哈希迁移)、单 Redis 集群要上分片集群(热点题 100 万 QPS 需要本地多级缓存)、Nginx 单层要加 L4 入口层、判题机池按地域 stamp 部署。答案方向对即可,关键是知道「哪里会先碎」。
自测三问
- 不看课文,默写这道题的五个估算数字及其推导链。
- 说出你的架构里三个组件的「双句过审」(解决什么 + 挂了怎么办)。
- 三阶段演进中,哪一阶段就能扛住推广活动?为什么不留到 Phase 3?
小结一句话
综合设计的核心是「结论先行、数字可溯、组件双句过审、演进可回退」——能分阶段讲清代价与故障行为,比一张炫技架构图有工程价值得多。
延伸
- 《System Design Interview》Vol.2:多组件综合题(含判题类系统)的完整范例
- Google SRE Workbook — Emergency Response:故障演练与应急响应的落地方法
- Microsoft Azure — Cloud Design Patterns 全目录:把本 track 出现过的模式对照原文再过一遍
- 本 track 已完结。下一步建议:① 把本课答卷扩写成正式设计文档并做一次同伴评审;② 按 system-design 路线图 回查 12 课覆盖的节点查漏;③ 进阶阅读 DDIA 第 2 部分与 Google SRE Book;④ 在本站用「系统设计」标签刷题,把四步法用到每一道新题上。