7AA7 博客

系统设计面试:如何展示思考过程

许清欢 UID:85
2026-10-01 19:12
32 2 1

系统设计面试考察的不是”你知道多少个组件”,而是你在信息不完全时如何推进:先确定边界,再给一个能跑的东西,然后逐步加压、逐步加固,并且全程能让别人跟上你的思路。

下文以一个具体题目贯穿:设计一个短链接服务。

前五分钟:把需求问清楚

拿到题目立刻画图是最常见也最可惜的失误。题目给的信息一定是不完整的,缺的部分就是你该问的。

四类必问:

1. 功能边界:只做跳转?要不要自定义短码?要不要统计?要不要过期?
2. 规模量级:日活多少?日均新建多少条?读写比多少?
3. 一致性要求:短码冲突怎么处理?跳转允许短暂不一致吗?统计允许丢吗?
4. 非功能约束:延迟目标?可用性目标?数据保留多久?有没有合规要求?

问完之后要自己收敛成一份可写在白板上的需求,并跟面试官确认:

假设:日均新建 1000 万条,读写比约 100:1
      跳转 P99 < 100ms,允许短码生成后最多 1 秒可见
      短码不要求用户自定义,长度 7 位,永久保留
      统计为异步,允许少量丢失

“假设”这个词要主动说出来。它表明你知道自己在填空,而不是误以为题目自带这些数字。

容量估算:量级对就行

估算的目的是决定架构形态,不是算准数字。做法是抓住数量级,把中间步骤说出来:

写:1000 万条/天 ≈ 120 条/秒(按 1e5 秒粗算),峰值按 5 倍 → 600 QPS
读:读写比 100:1 → 日均 10 亿次 ≈ 1.2 万 QPS,峰值 → 6 万 QPS

存储:单条记录(短码 7B + 原URL 200B + 元数据 100B)≈ 300B
      1000 万/天 × 300B ≈ 3GB/天 → 一年约 1TB

带宽:读请求响应很小(302 + 短 URL),按 500B 计
      6 万 QPS × 500B ≈ 30MB/s ≈ 240Mbps(峰值)

缓存:若按二八定律,热点 20% 承担 80% 读量
      热点数据量 = 20% × 1TB ≈ 200GB → 全放内存成本高,需要分层或只缓存 Top N

几个让估算显得可靠的细节:

  • 明确说”1 天 ≈ 10^5 秒”这类换算,让对方能跟着验算。
  • 峰值按均值的几倍估,并说明倍数是你假设的。
  • 遇到不确定的量(比如原 URL 平均长度),给出假设值而不是停下来。
  • 算完之后要回到结论:这些数字意味着什么。例如”读远大于写,所以缓存收益极高;总数据量 1TB 级,单机放不下,必须分片”。

只算数字不给结论,等于没算。

先画一个能跑的最小方案

不要一上来就摆上消息队列、多级缓存、读写分离。先给一个单体的、能正确工作的版本:

客户端 → 应用服务(无状态,2 台)→ 单个关系型数据库(主从)

写:POST /shorten → 生成短码 → 写入 DB → 返回短码
读:GET /:code → 查 DB → 302 重定向

画完立刻自己指出它的问题在哪里,这就是下一步扩展的路线图:

问题1:读量 6 万 QPS 全打到 DB,DB 撑不住       → 加缓存
问题2:写量虽小但一年 1TB,单机存不下            → 分片
问题3:短码生成是热点(唯一性约束)               → 独立发号
问题4:DB 单点故障                                → 主从切换 / 多副本

这个顺序传达的信息是:你知道每个组件是为解决什么而出现的。直接摆终态架构,等于把组件清单背了一遍。

逐步扩展,每层说明代价

缓存层:

在应用与 DB 之间加缓存,读路径先查缓存,miss 回源并回填
命中率假设 90% → 打到 DB 的读量降到 6000 QPS,单主库可承受
代价:缓存与 DB 不一致;需要处理缓存穿透(布隆过滤器/空值缓存)、
      击穿(热点 key 单飞)、雪崩(过期时间加抖动)
淘汰策略:LRU 或按访问时间;容量不足以存全部热点,只存近期

发号与短码生成:

方案 做法 代价
A 哈希 对原 URL + 盐做哈希取前 7 位 有冲突,需处理;同一 URL 可复用
B 自增 ID 转 62 进制 全局发号器给数字 ID,再编码 需要发号器,但无冲突、可排序
C 随机 + 查重 随机生成,写时判断唯一 高量下冲突重试增多

选 B 的话要说明:放弃了什么——放弃了”同一 URL 生成同一短码”的能力(除非额外维护反查表),换来无冲突和生成路径简单。发号器本身可用批量取号(一次取 1000 个 ID 缓存在本地)来降低对它的压力。

分片:

按短码首字母或一致性哈希分片到 N 个库
选一致性哈希,理由是扩容时迁移量小(约 1/N)
代价:跨片查询困难(本场景无此需求,可接受);需要路由层

每一层都要带”代价”或”引入了什么新问题”。这是区分背诵和设计的分水岭。

取舍要说出口

面试官真正在听的是你有没有意识到每个选择都有对立面。养成一个句式:

"我选 X,因为它解决了 <当前最紧的问题>;
 代价是 <它带来什么新问题>;
 如果 <某个条件变化>,我会换成 Y。"

例如:”选缓存 Aside 模式而不是写穿透,因为读远大于写;代价是短暂不一致,对本场景可接受;如果将来业务要求短码修改后立刻生效,我会改成写时失效或加版本号。”

主动说”不做什么”同样加分:”这一版我不做统计功能,它需要独立的写入路径和聚合存储,可以二期加。”

被质疑时的正确反应

被追问通常不是否定,而是想看你在压力下怎么推进。反应顺序:

1. 先确认对方担心的维度:"你是指 <延迟> 还是 <可用性>?"
2. 复述他的顾虑,确认理解一致
3. 给出应对,或者承认这是个真实问题并说明你会怎么验证/权衡
4. 如果你认为他担心的问题在本场景下不成立,说明理由并给出触发条件

具体化一点:

面试官:"这个方案 DB 挂了怎么办?"
低质量回答:"我们会做高可用。"
可接受的回答:"你担心的是单库故障导致写不可用。
  当前主从结构下,主库故障需要人工或自动切换,切换期间写不可用,
  读可以继续走从库和缓存。
  如果要求写也不能中断,我会改成多主或引入支持多写的存储,
  代价是冲突处理复杂度上升。这个题目里我按允许分钟级写不可用处理。"

第三种情况——真的不知道——直接说不知道,然后说你会怎么查、怎么验证。硬撑出来的答案通常漏洞更多。

常见失败模式

失败模式 表现 修正
堆组件 一上来画十几个框,说不出每个为什么在那 从最小方案起步,逐个加
无数字 全程”大概很大””应该够用” 至少给出 QPS 和存储量级
忽略失败路径 只讲正常流程,不讲超时、重试、降级 每个外部依赖都问”它挂了会怎样”
不给取舍 只说优点 每个选择配一句代价
被问题带跑 面试官问缓存就讲十分钟缓存,忘了主线 记下旁支,答完拉回主线
沉默画图 长时间不说话 边画边说,把中间推理讲出来
抢答 没问清需求就动手 前五分钟用于澄清

还有一条容易被忽略:时间分配。45 分钟里,需求澄清约 5 分钟,估算 5 分钟,最小方案 10 分钟,扩展 20 分钟,剩下时间留给追问与收尾(重复一遍最终方案和主要取舍)。

设计能力体现在”我在什么约束下、为了什么目标、放弃了什么”,这三句话能不能被清楚地说出来。

1.6k浏览量
32赞
2评论
2参与用户
8 分钟阅读时间

评论(2)

  • 周牧野 · 2026-10-01 21:46

    写得比官方文档好读,官方文档那叫一个绕。

    回复
  • 裴鹿 · 2026-10-02 06:30

    这篇文章救了我今晚的发版。

    回复