3318 字
约 11 分钟
18
S27-X20 · 系统设计题的作答框架
"设计一个短链服务""设计一个订单系统"——题目给的信息永远不够。这不是题目缺陷,是考点本身:在信息不足时,先澄清再作答,而不是凭印象画一张架构图。
面试官看的也不是你画了多少个方块。他看的是:你怎么把模糊需求切成可判断的问题、每个选择背后放弃了什么、以及当他说"流量涨十倍"时你能不能立刻指出哪一块先撑不住。
1. 这道题在考什么
| 能力 | 外显行为 | 失分表现 |
|---|---|---|
| 拆解 | 把系统切成有边界的模块,说清数据怎么流 | 一上来列组件名 |
| 量级感 | 主动估算、给出假设、指出数量级差异 | 全程不提规模,方案无穷大也无穷小 |
| 权衡 | 一个决策点至少给两个方案与代价 | 只讲"最佳实践" |
| 瓶颈预判 | 能说出先坏的是哪个组件、为什么 | 被追问时答"加机器" |
| 表达 | 边画边讲、先说结论再说细节 | 沉默画图 5 分钟然后念图 |
评分权重未必均匀,但没有任何一项能靠背架构图补上。
2. 六步框架
① 澄清需求与量级 (3–5 分钟,先问再画)
② 列出核心实体与接口 (3 分钟,确定数据模型的最小集)
③ 画主干链路 (5 分钟,只画写入和读取两条主线)
④ 指出瓶颈与扩展点 (5 分钟,说"哪先坏、怎么扩")
⑤ 数据一致性与失败处理 (5 分钟,事务边界、重试、幂等)
⑥ 收尾:监控与演进 (2 分钟,怎么知道它坏了,下一步往哪走)
| 步骤 | 一句话目标 | 常见跳过 |
|---|---|---|
| ① | 把模糊词变成数字 | 直接开画,后面所有讨论没有基准 |
| ② | 冻住数据模型,避免中途改口径 | 只画服务不定义实体 |
| ③ | 让别人能跟着走完一次请求 | 画了全量组件,主线反而看不清 |
| ④ | 主动暴露成本 | 假装没有瓶颈 |
| ⑤ | 说清"失败时数据会怎样" | 假设网络和存储永远成功 |
| ⑥ | 给出可观测性方案 | 讲完功能就结束 |
3. 澄清阶段问什么
不要问"有什么要求"这种空问题,要问能改变方案的问题:
| 问什么 | 为什么它能改变方案 |
|---|---|
| 读写比例大概多少 | 读多 → 缓存/从库;写多 → 分片、批量、异步 |
| 数据规模与增长(量级即可) | 决定单库够不够、要不要分区 |
| 延迟要求(P99 还是均值) | 决定同步/异步、能否跨机房 |
| 一致性要求:强一致还是可最终一致 | 直接决定能否用缓存、队列 |
| 可用性目标(几个 9) | 决定冗余级别与故障切换方式 |
| 是否需要多地域 | 决定数据同步拓扑 |
| 谁在用(内部 / 公网 / 第三方) | 决定限流、鉴权、多租户设计 |
| 时间与人力约束 | 决定什么时候可以"先不拆" |
推荐问法(一次问两三个,不要连珠炮):
"我按一个量级假设来设计:日活百万量级、读写比大约 20:1,
写入要求强一致、读取允许秒级延迟。这个假设可以吗?
如果不合适,你希望我按哪个量级重新推?"
把假设说出来并请求确认,比等对方给答案更有效——对方往往故意不给,看你怎么处理不确定性。
4. 量级估算:给假设和算式,不背数字
估算的意义不是算对,而是暴露数量级差异(内存 vs 磁盘、单机 vs 集群、同步 vs 批量的量级差)。
假设:日活 100 万,人均每天 20 次读、1 次写
平均读 QPS = 1e6 × 20 / 86400 ≈ 231
平均写 QPS = 1e6 × 1 / 86400 ≈ 12
峰值系数按 8 倍(早晚高峰)→ 读约 1.8k QPS,写约 100 QPS
存储:每次写入 2 KB,日增 100 万 × 2 KB ≈ 2 GB/天
保留 1 年 ≈ 0.7 TB,加索引与副本,单机磁盘可承受
若每条记录平均被读 50 次/天,热数据可能只占总量几个百分点
→ 缓存策略:缓存热点,而不是全量
| 估算项 | 怎么得 | 用途 |
|---|---|---|
| QPS | 用户数 × 人均请求 / 86400 × 峰值系数 | 判断单机还是集群 |
| 存储量 | 条数 × 单条大小 × 保留期 × 副本数 | 判断单库够不够 |
| 带宽 | QPS × 响应体大小 | 判断是否被网络卡住 |
| 连接数 | QPS × 平均处理时长 | 判断线程/连接池上限 |
| 缓存占比 | 热数据比例(经验:远小于总量) | 决定缓存容量 |
明确说"峰值系数我按 8 倍估,实际要看业务曲线"。所有数字都挂在假设上,改假设就改结论——这就是量级感的正确呈现方式。
5. 存储选型:先看访问模式,再挑引擎
顺序不能反。先问"怎么读怎么写",再决定用什么。
| 访问模式 | 倾向选择 | 理由 |
|---|---|---|
| 按主键点查、量极大、可容忍最终一致 | KV / 宽表 | 点查是它们的强项,水平扩展简单 |
| 多条件组合查询、事务 | 关系库 | 索引与事务成熟,代价是横向扩展难 |
| 全文检索、相关性排序 | 倒排索引引擎 | 模糊匹配与打分,别用它当主存储 |
| 时序、按时间范围聚合 | 时序库 / 列存 | 压缩率高,聚合快 |
| 关系建模、少量数据强一致 | 关系库单机 | 没到规模就别上分布式 |
| 大对象(图片、附件) | 对象存储 + 元数据入库 | 别把二进制塞进数据库行 |
判断顺序(按重要性):
1) 读写模式:点查 / 范围 / 聚合 / 全文
2) 一致性:需要事务吗?跨行吗?
3) 规模:单机内存能否容纳热点?
4) 查询灵活性:以后会不会要新的查询维度?
5) 团队运维能力:这套东西谁来值班?
第 4 条常被忽略:选型时只看今天的查询,半年后加一个报表需求就得改架构。
6. 缓存与队列:什么时候才该引入
两者都是用复杂度换性能,引入前先确认复杂度付得起。
| 引入缓存的前提 | 引入队列的前提 |
|---|---|
| 读多写少且热点集中 | 写操作可异步且不要求立刻可见 |
| 能接受短时间不一致 | 消费端幂等(重复消费不产生副作用) |
| 有明确的失效策略(TTL/主动删) | 有失败重试与死信处理 |
| 有人盯命中率与穿透 | 有人盯堆积与消费延迟 |
// 缓存读路径:先缓存,miss 再查库,回填带随机化 TTL 防雪崩
String v = cache.get(key);
if (v == null) {
v = db.load(key);
if (v != null) cache.set(key, v, ttl + ThreadLocalRandom.current().nextLong(jitter));
}
需要评估的反面:缓存一致性、热门 key 打爆单分片、缓存与库双写顺序、队列堆积时的降级策略。引入队列就等于引入"消息可能重复、可能乱序、可能延迟"这三个新问题,必须在设计里回答它们。
7. 一致性:把取舍讲成代价
不要只说"我们用了最终一致",要说明代价落在哪儿、谁来兜底。
| 方案 | 得到什么 | 付出什么 |
|---|---|---|
| 单库事务(本地事务) | 强一致、实现简单 | 扩展受单库限制 |
| 两阶段提交 | 跨库强一致 | 协调者单点、性能差、可用性差 |
| 本地消息表 / 事务消息 | 最终一致、可靠投递 | 需要消费端幂等 + 对账 |
| 补偿(Saga) | 无长事务、吞吐好 | 必须写补偿逻辑,且补偿本身可能失败 |
| 事件溯源 | 完整审计、可重放 | 查询复杂、快照与版本管理成本高 |
表达模板:
"写库存和写订单必须原子,所以我把它们放在同一个库的同一个事务里。
下游的通知走本地消息表,异步投递——所以通知可能延迟几百毫秒,
这里我们接受最终一致,兜底是每天的对账任务。
如果对账发现差异,会走人工确认流程。"
关键是把"不一致窗口"说出来,并说明谁负责发现不一致。
8. 失败与降级
面试里主动讲失败处理,分数通常高于讲功能。要覆盖三类:
依赖失败:下游超时 → 快速失败 + 熔断;降级返回缓存或默认值
自身失败:实例挂掉 → 健康检查摘除;有状态部分需要有主备与切换时间说明
数据失败:写入部分成功 → 幂等键 + 重试 + 对账;避免"重试即重复"
| 失败点 | 应答要点 |
|---|---|
| 缓存不可用 | 直接落库会有击穿风险 → 限流 + 短时降级返回旧值 |
| 队列积压 | 监控 lag,消费端扩容;超阈值时走同步兜底路径 |
| 主库切换 | 说明切换时长与切换到从库后的写行为 |
| 第三方接口不可用 | 超时阈值、重试次数、熔断后返回什么 |
9. 表达技巧
- 边画边讲:画到哪讲到哪,别沉默作图。
- 先说结论:"这里我选 B,理由是……" 再展开细节。
- 主动说取舍:每个决策点留一句"代价是 X"。
- 留追问接口:讲完一层停一下,"这里可以再往下讲分片键的选择,需要展开吗?"
- 时间管理:20 分钟里,澄清与建模不超过 8 分钟,留一半时间给权衡与追问。
- 允许改变结论:被指出漏洞时,快速修正比死扛更好;修正过程本身就是考点。
10. 常见坑
| 坑 | 为什么扣分 | 改法 |
|---|---|---|
| 一上来画微服务 | 没量级就拆,等于先付复杂度 | 先澄清,单体能撑就不拆 |
| 全程不提规模 | 方案没有适用边界 | 给假设 + 算式 + 量级 |
| 只列组件不讲数据流 | 说明没真正想过链路 | 用一条请求走完全程 |
| 被追问瓶颈答"加机器" | 没有定位能力 | 指出先坏的组件与原因 |
| 缓存/队列当万能药 | 忽略一致性代价 | 说清引入后新增的三个问题 |
| 讲完功能就结束 | 缺可观测性 | 补指标、日志、追踪与告警 |
11. 边界:这套框架的适用范围
| 场景 | 说明 |
|---|---|
| 编码题、算法题 | 不适用,直接写代码 |
| 明确给定规模的题 | 可跳过估算,但仍要讲瓶颈 |
| 偏运维/故障题 | 侧重第五、六步:一致性与观测 |
| 实际架构评审 | 框架一样,但需要真实成本与人月估算 |
一句话收口:先问再画,先估再选;每个决策给方案和代价,每个方案说清什么时候会坏。
12. 自测
- 澄清阶段为什么要问读写比例,它改变了哪两个具体选型?
- 量级估算的目标是算准数字吗?它真正的用途是什么?
- 存储选型的判断顺序里,为什么"访问模式"排在"引擎是否流行"前面?
- 引入消息队列后新增的三个问题是什么?
- 讲"最终一致"时必须同时交代哪两件事?
- 被追问"流量涨十倍怎么办",第一步该说什么?
S27-X20 · 系统设计题的作答框架
https://www.clxhxhhr.top/posts/5924/ 评论
0 条
还没有评论,先写一条吧。