cswork / 系统设计

第 1 章 / 共 57 章 · 约 6 分钟

第 1 章:系统设计面试框架

本页目录
  1. 总览:五步与时间分配
  2. 第 1 步:需求澄清
  3. 第 2 步:核心实体
  4. 第 3 步:API 设计
  5. 第 4 步:高层设计
  6. 第 5 步:深挖
  7. 粗略估算(Back-of-the-Envelope)
  8. 不同职级的期望
  9. 常见失分点
  10. 章节测验

系统设计面试通常 45–60 分钟,题目往往只有一句话:“设计一个短链接服务”“设计 Twitter 首页 Feed”。这类题没有标准答案。面试官主要看三件事:你能不能把模糊的问题拆清楚,能不能在约束下做出合理取舍,能不能边做边把思路讲明白。

框架不是用来背的模板,它的作用是让你不漏掉关键环节,并且把时间花在最能拿分的地方。本章介绍一套五步流程,以及贯穿全程的估算方法和不同职级的评价差异。

总览:五步与时间分配

以 45 分钟为例(去掉开场寒暄和结尾提问,实际可用约 35–40 分钟):

步骤建议时长产出
1. 需求澄清~5 分钟3–5 条功能需求 + 量化的非功能需求
2. 核心实体~2 分钟系统里的主要“名词”
3. API 设计~5 分钟对外接口签名
4. 高层设计~10–15 分钟能跑通所有功能需求的架构图
5. 深挖~10–15 分钟针对瓶颈和非功能需求的优化
答题框架:五个步骤与非功能需求
滚动到此处后加载图表…
图表可横向滚动,展开后可缩放查看。
查看图表源码
flowchart LR
  accTitle: 答题框架:五个步骤与非功能需求
  req["需求"] --> ent["实体"] --> api["API"] --> hld["高层设计"] --> deep["深挖"]
  req --> nfr["非功能需求"]
  nfr --> hld
  nfr -->|"决定深挖往哪个方向走"| deep

第 1 步:需求澄清

功能需求用“用户可以……”的句式写,只保留 3–5 条核心功能。以 Twitter Feed 为例:

  • 用户可以发推文(文字 + 图片)
  • 用户可以关注其他用户
  • 用户可以查看关注对象的推文,按时间倒序排列

同时明确说出不做什么(out of scope),比如“私信、广告、搜索先不考虑”。这样既省时间,也能让面试官看到你有范围意识。

非功能需求要量化,而且要和这道题相关:

  • 规模:DAU 多少?读写比多少?(Feed 类系统通常读远多于写)
  • 延迟:写成“Feed 加载 p99 < 300ms”,而不是“要快”
  • 可用性与一致性:发推后几秒内别人能看到即可,说明可以接受最终一致
  • 持久性:已发布的推文不能丢

常见误区:

  • 问一长串与设计无关的问题,10 分钟就过去了。
  • 面试官说“你来定”时犹豫不决。正确做法是直接给出合理假设并说出来:“我假设 2 亿 DAU,读写比约 100:1。”

第 2 步:核心实体

列出系统中的主要对象即可,例如 User、Tweet、Follow、Media。这一步不需要写完整表结构,字段可以在高层设计时按需补充。它的作用是和面试官对齐用词,同时为 API 设计打基础。

第 3 步:API 设计

默认用 REST;需要服务端主动推送时考虑 WebSocket 或 SSE;内部服务之间可以提 gRPC。

POST /v1/tweets               body: { text, media_ids }  → { tweet_id }
POST /v1/users/{id}/follow
GET  /v1/feed?cursor=xxx&limit=20                        → { items, next_cursor }

几个加分细节:

  • 当前用户的身份从认证 token 中获取(请求头里的 JWT 或 session),不要让客户端在请求体里自己填 user_id,否则任何人都能冒充别人。
  • 列表接口用游标分页(cursor)。数据频繁插入时,offset 分页容易出现重复或遗漏,深翻页也更慢。
  • 写接口考虑幂等,例如让客户端带一个 idempotency key,防止网络重试导致重复发帖。

第 4 步:高层设计

目标是先拿出一个能工作的方案:逐条走通功能需求的数据流,不要一开始就追求完美。

Twitter Feed 的高层设计
滚动到此处后加载图表…
图表可横向滚动,展开后可缩放查看。
查看图表源码
flowchart TD
  accTitle: Twitter Feed 的高层设计
  client["Client"] --> lb["LB / Gateway"]
  lb --> tweet["Tweet Service"]
  lb --> feed["Feed Service"]
  tweet --> tdb[("Tweet DB")]
  feed --> fcache[("Feed Cache")]
  tdb -.->|"异步"| fcache

讲解时沿着请求路径叙述:“用户发推 → 网关鉴权 → Tweet Service 写库 → 异步更新粉丝的 Feed 缓存……”。遇到需要优化的地方,先标注“这里稍后深挖”,保持节奏,别在这一步陷进细节。

第 5 步:深挖

深挖的方向由非功能需求决定:规模带来扩展问题,延迟要求带来缓存问题,可用性要求带来单点故障问题。每个深挖点可以按下面的结构展开:

  1. 问题:例如“某个大 V 有 1 亿粉丝,写扩散(fan-out on write)一次就要写 1 亿条”
  2. 方案:至少给两个选项,例如写扩散、读扩散、二者混合
  3. 权衡:延迟、存储、实现复杂度、一致性
  4. 决定:结合本题需求选一个,并说明理由

Senior 及以上的候选人应该主动提出深挖点,同时也要灵活跟随面试官感兴趣的方向。

粗略估算(Back-of-the-Envelope)

估算只有一个目的:支撑设计决策。如果算出来的数字不会改变你的方案,就一句话带过。

量近似值
一天的秒数86,400 ≈ 10⁵
每天 100 万次请求≈ 12 QPS
每天 1 亿次请求≈ 1,200 QPS
峰值 QPS平均值 × 2~5(视业务而定)
内存访问 / SSD 随机读 / 同机房往返 / 跨洲往返~100ns / ~100μs / ~0.5ms / ~100ms 以上

示例:2 亿 DAU,每人每天刷 20 次 Feed → 每天 40 亿次读 ≈ 4×10⁹ ÷ 10⁵ = 4 万 QPS(精确值约 4.6 万),峰值按 3 倍算约 12–14 万 QPS。结论:单个数据库扛不住,需要缓存加读副本。这就是用估算推动设计决策。

存储:每天新增 5,000 万条推文,每条约 1KB → 50GB/天 → 一年约 18TB(不含图片视频)。说明几年的数据单机放不下,需要考虑分片;媒体文件则应放到对象存储。

不同职级的期望

维度Mid(L4/E4)Senior(L5/E5)Staff(L6/E6+)
主导程度在提示下推进,面试官引导较多自己掌控节奏,很少需要提示全程主导,像在主持一场技术评审
高层设计能做出可行方案方案完整,每个组件都有选择理由方案简洁,主动避免不必要的复杂度
深挖1–2 个点,讲清基本原理多个点,能比较方案、讲清权衡一眼找到最关键的难点,并讨论演进、运维、成本
技术深度知道常见组件的用途了解组件内部机制和适用边界能结合实战经验指出方案在生产环境中的坑

注意,Staff 不等于“用更多组件”。很多时候 Staff 级别的表现恰恰是说出:“这里一个 Postgres 就够了,原因是……”。

常见失分点

  • 跳过需求直接画图,做到一半发现方向错了
  • 堆砌名词:Kafka、Redis、Cassandra 全用上,却说不出为什么
  • 深挖只给一个方案,没有任何权衡
  • 时间失控:过了 20 分钟还在聊需求
  • 长时间沉默:思考时也要把思路说出来,面试官没法给沉默打分

章节测验

6 道选择题
  1. 完成需求澄清之后、开始画高层设计之前,最合适的下一步是什么?
  2. 某应用 1 亿 DAU,每个用户每天发起约 10 次读请求。平均读 QPS 大约是多少?
  3. 下面哪条非功能需求的表述最好?
  4. 设计 POST /v1/tweets 接口时,发帖用户的身份应该从哪里获取?
  5. Staff 级别与 Senior 级别在系统设计面试中最主要的区别是什么?
  6. 关于粗略估算,下面哪种做法最合适?

容量估算练习

误差 ±30% 以内算对

写 = 生成一个短链;读 = 一次跳转

DAU
1 亿
每人每天写入
0.1
读 : 写
100 : 1
每次写入 / 读取的大小
500 B
保留期
5 年
峰值 / 平均
× 3
单台应用服务器
5,000 QPS
单台存储服务器
10 TB · 3 副本
次/秒
次/秒
TB
MB/s
台

可以写 46000、4.6万、46k 或 4.6e4。

章节讨论

正在加载讨论…