主题
系统设计四步分析法与估算
本文是系统设计系统学习系列的 L1 入门篇。前置:无。 学完可以配合面试题食用:01-short-url-service、20-system-design-tradeoff-and-limit
为什么系统设计需要"套路"
面试或工作中拿到一个设计题,最常见的反应是"这功能我熟,但先从哪里开始说?"上来就画架构图的人,往往漏掉最关键的前提:需求没对齐、规模没定。
四步分析法把模糊的设计题拆成可执行的步骤:澄清需求 → 估算规模 → 高层架构 → 深挖瓶颈。不是套路,是防漏的 checklist。一个短链系统、一个 feed 流、一个秒杀,都能用这四步走一遍。
第一步:澄清需求——先问再答,别自嗨
需求阶段最容易犯的错是"默认理解"。面试官说"设计一个短链服务",你可以直接问:
- 用户规模:DAU 多少?100 万还是 1 亿,方案差很远
- 读写比:写一次短链被读多少次?对标典型的 1:100 还是 1:10000
- 一致性要求:短链要实时生效吗?还是 TTL 过期后允许短暂不一致
- 延迟要求:用户点短链,期望多快跳转?200ms 以内还是秒级
- 预算约束(实战中才有):自建还是用云服务,决定了冗余等级
这些问题也可以反向验证:面试官说"日活 1000 万",那短链每天的写入量大概是多少?用估算来反推。
第二步:估算规模——用拇指法则算数字
估算不追求精确,目的是让后续设计有数据支撑。常用的拇指法则:
| 指标 | 数量级 |
|---|---|
| 内存操作 | 10^7 ops/s(单机) |
| SSD 随机读 | 10^4 ops/s |
| 网络 RTT(同机房) | 0.5ms |
| 跨 DC 延迟 | 30-60ms |
| 磁盘顺序写 | 10^5 ops/s |
DAU → 请求量的估算公式:
- 日请求数 = DAU × 每日人均请求数 × 峰值系数
- 峰值系数:3-10 倍,取决于业务波动(短链均匀 3 倍,秒杀可能 10 倍以上)
- 带宽 = 平均响应大小 × QPS × 8(bit 转换)
案例:日活千万 feed 系统的完整估算
DAU = 1000 万
人均刷 feed 次数 = 50 次/天
每日总请求 = 1000w × 50 = 5 亿次
峰值 QPS = (5亿 / 86400) × 3 ≈ 17000 QPS
存储:每条 feed 消息 2KB,每天新增 500 万条 → 10GB/天
1 年存储 ≈ 3.6TB(冷热分离后热数据约 1.2TB)
带宽:下游响应 50KB(含图片缩略图),17000 × 50KB × 8 = 6.8 Gbps这组数字告诉你:17000 QPS 单机 Redis 扛不住但加缓存层的 MySQL 集群可以;6.8 Gbps 带宽需要多网卡或负载均衡。数字有了,设计才有依据。
第三步:高层架构——画好组件和交互
先画粗粒度架构,不纠结细节。用一个短链系统来走一遍:
mermaid
flowchart LR
Client[客户端] --> LB[负载均衡]
LB --> Web[Web 服务层]
Web --> Cache[Redis 缓存]
Web --> DB[(MySQL 集群)]
Web --> Counter[ID 生成器]
Counter --> DB短链的核心流程:
- 生成短链:全局 ID 生成器(雪花/号段)拿到 ID,编码成短字符串,写 DB 和缓存
- 访问短链:查缓存 → 查 DB → 302 跳转,缓存命中则跳过 DB
第一版架构只画核心组件,不引入 MQ、不引入 ES,等估算出瓶颈再加。
第四步:深挖瓶颈——用数字找薄弱环节
回到前面的估算数据。短链系统日活 1000 万,写入 QPS 约 50(用户每天平均 5 条短链),读取 QPS 约 5000:
- 写入 QPS 50:单机 MySQL 完全够,优化点在于 ID 生成器不依赖 DB 自增(号段模式就够了)
- 读取 QPS 5000:Redis 缓存命中率 99% 的情况下,后端 DB 只有 50 QPS,毫无压力
- 瓶颈在哪:短链跳转的 302 请求本身,Web 层需要足够长的连接?不,短链是 stateless 的,水平扩展即可
java
// 短链 ID 生成:号段模式(每批次预取 1000 个 ID)
public class SegmentIdGenerator {
private final DataSource dataSource;
private final AtomicLong currentId = new AtomicLong(0);
private final AtomicLong maxId = new AtomicLong(0);
private final int step = 1000;
public synchronized long nextId() {
if (currentId.get() >= maxId.get()) {
// 从 DB 取下一批号段
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(
"UPDATE id_segment SET max_id = max_id + ? WHERE biz_tag = ?")) {
ps.setInt(1, step);
ps.setString(2, "short_url");
ps.executeUpdate();
// 重新读取
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT max_id FROM id_segment WHERE biz_tag = 'short_url'")) {
rs.next();
long newMax = rs.getLong(1);
currentId.set(newMax - step);
maxId.set(newMax);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return currentId.getAndIncrement();
}
public String encode(long id) {
// Base62 编码:0-9a-zA-Z 共 62 字符
String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuilder sb = new StringBuilder();
while (id > 0) {
sb.append(chars.charAt((int) (id % 62)));
id /= 62;
}
return sb.reverse().toString();
}
}号段模式的好处:DB 压力极小(每 1000 个 ID 才一次 DB 写),且 ID 生成服务重启不丢号段。配合 Base62 编码,6 位字符能覆盖约 568 亿个短链,完全够用。
数字速查卡——面试时随手用
以下数字不需要背,但要有"数量级直觉":
| 操作 | 延迟 | 单机 QPS 上限 |
|---|---|---|
| 内存读(Redis) | 0.1ms | 10 万+ |
| 本地内存读 | 0.01ms | 100 万+ |
| SSD 随机读 | 2-5ms | 1 万 |
| MySQL 简单查询(非缓存) | 5-10ms | 5000 |
| 同机房 RPC | 0.5-2ms | 1 万+ |
| 跨 DC RPC(同区域) | 10-30ms | 3000 |
| 磁盘顺序写 | 0.1ms | 10 万+ |
常见误区与小结
- 误区 1:估算数字精确到个位。估算本质是"数量级论证",17000 QPS 和 18000 QPS 不影响设计;但 1700 和 17000 差一个数量级,方案完全不同
- 误区 2:上来就画复杂架构。Redis + MQ + ES + 分库分表全上,面试官直接问"你确定你的规模需要这些?"防止过度设计的最好方法就是先有估算数字
- 误区 3:需求阶段不问人。实战中这是最致命的,一个月后返工;面试中不问会扣"沟通能力"分
- 误区 4:把估算只当面试技巧。线上真实项目的容量规划比面试更严格,从 0 到 1 的估算习惯能帮你避免上线就 OOM
- 误区 5:忽视带宽估算。存算分离架构下,网络带宽往往先于 CPU 成为瓶颈——6.8 Gbps 的流量需要万兆网卡支持
小结:四步分析法不是银弹,它是一个防漏 checklist。拿到任何设计题,先用这四步走一遍,方向对了再深入细节。下一篇进入系统设计核心组件,看负载均衡、缓存、消息队列和 CDN 各自解决什么问题,以及什么规模才值得引它们。
参考
参考:《Designing Data-Intensive Applications》- Martin Kleppmann(第六章分区与第九章一致性与共识的估算方法) 参考:系统设计面试 01 短链服务 - 四步法的完整走读案例