01 | 初识系统设计 —— 为什么需要系统设计、设计方法论、估算技巧
一个好的系统不是凭空产生的,它源于对问题的深刻理解与对方案的反复权衡。
开篇自测
在正式开始之前,请先回答以下三个问题,检验你对系统设计的初始认知:
- 当面对一个全新的业务需求时,你会从哪几个维度来思考系统的整体方案?
- 如果产品经理告诉你”日活用户 500 万”,你能据此估算出系统需要承载的 QPS 吗?
- 你认为”架构设计”和”编码实现”之间最本质的区别是什么?
带着这些问题,我们正式进入系统设计的世界。
一、为什么需要系统设计
1.1 从一次线上事故说起
某社交平台在一次运营活动中,预期流量为日常的 5 倍。开发团队仅对数据库做了简单的主从配置,便上线了活动。活动开始后 10 分钟,数据库连接池被打满,缓存大面积失效,整个系统响应时间从 200ms 飙升至 15 秒,最终不得不紧急下线活动。
事后复盘,团队发现问题的根源并非代码 Bug,而是缺乏系统性的架构思考:没有做容量估算、没有设计降级方案、没有对热点数据做缓存预热。
这个案例揭示了一个事实:代码写得再好,如果系统设计不合理,在规模和复杂度面前都将不堪一击。
1.2 系统设计解决什么问题
系统设计关注的核心问题可以用三个关键词概括:
- 规模:当用户从 1 万增长到 1000 万时,系统如何平滑扩展?
- 复杂度:当业务模块从 3 个增长到 30 个时,如何保持系统的可维护性?
- 可靠性:当某个组件发生故障时,如何保证整体服务不受影响?
系统设计的核心关注点
+------------------------------------------------+
| |
| 规模 (Scale) |
| - 用户量增长 |
| - 数据量膨胀 |
| - 请求量激增 |
| |
| 复杂度 (Complexity) |
| - 业务逻辑交织 |
| - 技术栈多样 |
| - 团队协作困难 |
| |
| 可靠性 (Reliability) |
| - 硬件故障 |
| - 软件缺陷 |
| - 人为操作失误 |
| |
+------------------------------------------------+
1.3 系统设计与编码的本质差异
编码的核心思维是逻辑与实现——给定输入,产生正确的输出。而系统设计的核心思维是判断与取舍——在多种可行方案中,选择最适合当前场景的那个。
一个典型的例子:实现用户注册功能,编码层面关心的是参数校验、密码加密、数据入库;而系统设计层面关心的是——注册信息存在哪里(MySQL 还是 MongoDB)、如何防止重复注册(唯一索引还是分布式锁)、注册后的消息通知是同步还是异步、如果注册量激增到每秒 1 万次该怎么办。
编程解决的是”如何做对”,系统设计解决的是”如何做好”。
二、系统设计方法论
2.1 四步设计法
面对任何一个系统设计问题,都可以按照以下四个步骤来展开:
第一步:明确需求与约束
- 功能需求:系统要做什么?核心用例有哪些?
- 非功能需求:性能指标、可用性目标、数据一致性要求
- 约束条件:预算、团队规模、技术栈限制、上线时间
第二步:粗略估算
- 用户规模、数据规模、请求量级
- 存储需求、带宽需求、计算资源需求
第三步:概要设计
- 划分核心模块和组件
- 确定数据流向和通信方式
- 绘制系统拓扑图
第四步:深入设计与权衡
- 针对核心模块做详细方案
- 识别瓶颈点并提出优化策略
- 讨论方案的 Trade-off
明确需求 粗略估算 概要设计 深入设计
+-----------+ +-----------+ +-----------+ +-----------+
| 功能需求 | --> | 用户规模 | --> | 模块划分 | --> | 详细方案 |
| 非功能需求| --> | 数据规模 | --> | 数据流向 | --> | 瓶颈识别 |
| 约束条件 | --> | 资源需求 | --> | 拓扑结构 | --> | Trade-off |
+-----------+ +-----------+ +-----------+ +-----------+
2.2 需求分析的技巧
很多人在做系统设计时,第一反应是”用什么技术”,但更重要的是先问清楚”要解决什么问题”。
一个好的需求分析应该覆盖以下维度:
| 维度 | 关键问题 | 示例 |
|---|---|---|
| 用户 | 谁在用?用户量级? | DAU 500 万,峰值 QPS 5000 |
| 功能 | 核心功能是什么? | 发消息、查历史、推送通知 |
| 数据 | 数据量多大?增长速率? | 日增 1 亿条消息,保留 3 年 |
| 性能 | 延迟要求?吞吐量要求? | P99 延迟 < 200ms |
| 可用性 | 能接受多长时间宕机? | 年可用性 99.99%(年宕机 < 53 分钟) |
| 一致性 | 数据能否有短暂不一致? | 允许最终一致性,延迟 < 5 秒 |
2.3 “合适”优于”先进”
系统设计中最常见的误区之一是过度设计。一个日活 1000 人的内部工具,不需要微服务架构;一个读多写少的博客系统,不需要分库分表。
三条实用原则:
- 简单原则:能用简单方案解决的问题,不要引入复杂方案
- 演进原则:先满足当前需求,再根据增长趋势逐步优化
- 适度原则:预留 2-3 倍的容量冗余,而非 100 倍
三、容量估算:系统设计的第一课
3.1 为什么要做容量估算
容量估算是系统设计的起点。它决定了你需要多少台服务器、多大的数据库、多宽的带宽。没有估算,一切架构决策都是空中楼阁。
容量估算的目标不是精确到个位数,而是确定数量级——是 100 QPS 还是 10000 QPS,因为这两者的架构方案完全不同。
3.2 常用估算数据
在做估算之前,你需要记住一些常用的基准数据:
延迟数据(2020 年代参考值,量级参考,实际因硬件型号和配置而异)
| 操作 | 耗时 |
|---|---|
| L1 缓存引用 | 1 ns |
| L2 缓存引用 | 4 ns |
| 主内存引用 | 100 ns |
| SSD 随机读 | 16 us |
| 机械硬盘寻道 | 4 ms |
| 同机房网络往返 | 0.5 ms |
| 跨城网络往返 | 30 ms |
| 跨洲网络往返 | 150 ms |
吞吐量参考值
| 组件 | 参考吞吐量 |
|---|---|
| 单台 MySQL(普通查询) | 3000-5000 QPS |
| 单台 Redis(简单命令) | 80000-100000 QPS |
| 单台 Nginx(静态文件) | 50000-100000 QPS |
| 单台应用服务器(业务逻辑) | 500-2000 QPS |
存储参考值
| 数据 | 大小 |
|---|---|
| 一条推文(280 字 UTF-8) | 最多约 1120 字节 |
| 一张缩略图 | ~20 KB |
| 一张高清图 | ~500 KB |
| 一分钟标清视频 | ~10 MB |
3.3 估算实战:社交平台消息系统
背景假设:
- DAU(日活用户):1000 万
- 每个用户每天平均发送 20 条消息
- 每条消息平均 200 字节
- 消息保留 5 年
- 读写比 10:1
QPS 估算:
写入 QPS:
日消息总量 = 1000 万 x 20 = 2 亿条/天
平均写入 QPS = 2 亿 / 86400 ≈ 2300 QPS
峰值写入 QPS = 2300 x 3(峰值系数)≈ 7000 QPS
读取 QPS:
平均读取 QPS = 2300 x 10 = 23000 QPS
峰值读取 QPS = 23000 x 3 ≈ 70000 QPS
存储估算:
日存储增量 = 2 亿 x 200 字节 = 40 GB/天
年存储增量 = 40 GB x 365 ≈ 14.6 TB/年
5 年存储量 = 14.6 TB x 5 ≈ 73 TB
带宽估算:
峰值入站带宽 = 7000 x 200 字节 = 1.4 MB/s ≈ 11.2 Mbps
峰值出站带宽 = 70000 x 200 字节 = 14 MB/s ≈ 112 Mbps
初步结论:
- 写入 QPS 7000,一台 MySQL 难以承载,需要考虑分库或引入消息队列削峰
- 读取 QPS 70000,非常适合引入 Redis 缓存层
- 73TB 存储量,需要考虑数据分片和冷热分离
3.4 常用数量级速算技巧
记住以下几个关键换算,能让你在面试和讨论中快速估算:
| 换算 | 值 |
|---|---|
| 1 天有多少秒 | ~86400 ≈ 10^5(约 10 万) |
| 1 天有多少毫秒 | ~86400000 ≈ 10^8(粗略近似,实际为 8.64×10^7) |
| 2.5 亿 / 天 ≈ ? QPS | ≈ 3000 QPS |
| 1 QPS 对应 ? 次/天 | ≈ 86400 ≈ 10 万次/天 |
| 1 MB/s ≈ ? Mbps | ≈ 8 Mbps |
| 1 TB ≈ 多少条 1KB 记录 | ≈ 10 亿条 |
四、系统设计中的核心概念
4.1 性能指标体系
衡量一个系统好不好,需要一套可量化的指标:
响应时间(Latency)
- P50:50% 的请求在该时间内完成(中位数)
- P95:95% 的请求在该时间内完成
- P99:99% 的请求在该时间内完成
为什么要关注尾部延迟(P99/P999)?因为体验最差的那 1% 用户,往往是你最重要的用户——他们的请求数据量更大、操作更复杂。
吞吐量(Throughput)
- QPS(Queries Per Second):每秒查询数
- TPS(Transactions Per Second):每秒事务数
- RPS(Requests Per Second):每秒请求数
可用性(Availability)
用”几个 9”来衡量:
| 等级 | 可用性 | 年停机时间 |
|---|---|---|
| 2 个 9 | 99% | 3.65 天 |
| 3 个 9 | 99.9% | 8.76 小时 |
| 4 个 9 | 99.99% | 52.6 分钟 |
| 5 个 9 | 99.999% | 5.26 分钟 |
一般来说,核心支付系统追求 4 个 9,普通业务系统 3 个 9 即可。
4.2 可扩展性的两种方式
垂直扩展 (Scale Up) 水平扩展 (Scale Out)
+-------------------+ +-------+ +-------+ +-------+
| | | | | | | |
| 更强的单机 | | 机器1 | | 机器2 | | 机器3 |
| - 更多 CPU | | | | | | |
| - 更大内存 | +-------+ +-------+ +-------+
| - 更快磁盘 | +-------+ +-------+ +-------+
| | | | | | | |
+-------------------+ | 机器4 | | 机器5 | | 机器6 |
| | | | | |
+-------+ +-------+ +-------+
优点: 简单,无需改代码 优点: 理论上可无限扩展
缺点: 存在物理上限,成本高 缺点: 增加系统复杂度
现代互联网系统几乎都以水平扩展为主,因为垂直扩展存在天花板,而水平扩展则可以按需线性增长。
4.3 单点故障与冗余
**单点故障(SPOF)**是系统设计中最大的隐患。所谓单点,就是某个组件一旦失效,整个系统就无法工作。
消除单点的核心手段是冗余:
- 应用层冗余:多实例部署 + 负载均衡
- 数据层冗余:主从复制 + 自动故障转移
- 网络层冗余:多线路接入 + DNS 故障切换
消除单点故障的典型架构
用户请求
|
+---------+
| DNS |(多 IP 轮询)
+---------+
/ \
+--------+ +--------+
| LB-1 | | LB-2 | 负载均衡层(主备)
+--------+ +--------+
/ | \
+-----+ +-----+ +-----+
|App1 | |App2 | |App3 | 应用层(多实例)
+-----+ +-----+ +-----+
\ | /
+--------+ +--------+
|Master | |Slave | 数据层(主从复制)
| DB | | DB |
+--------+ +--------+
五、架构设计的常见模式
5.1 分层架构
分层架构是最经典的架构模式,它将系统按职责划分为多个层次,每一层只与相邻层交互。
+--------------------------------------------------+
| 表现层 (Presentation) |
| Web 页面 / 移动端 / API 网关 |
+--------------------------------------------------+
|
+--------------------------------------------------+
| 业务逻辑层 (Business) |
| 核心业务处理 / 规则引擎 / 工作流 |
+--------------------------------------------------+
|
+--------------------------------------------------+
| 数据访问层 (Data Access) |
| ORM / DAO / Repository |
+--------------------------------------------------+
|
+--------------------------------------------------+
| 存储层 (Storage) |
| MySQL / Redis / MongoDB / ES |
+--------------------------------------------------+
分层架构的优势在于职责清晰、易于测试、可独立替换。但也存在性能损耗(每层调用都有开销)和过度抽象的问题。
5.2 客户端-服务器模式
这是 Web 应用中最基础的模式。客户端负责展示和交互,服务器负责业务处理和数据存储。
在移动互联网时代,这种模式进一步演化为:
- 瘦客户端:大部分逻辑在服务器端(如传统 Web 页面)
- 胖客户端:客户端承担较多逻辑(如 SPA 单页应用、Native App)
- BFF 模式:为不同客户端提供专门的后端服务层
5.3 事件驱动架构
事件驱动架构通过异步消息在组件之间传递信息,实现松耦合:
生产者 A 消费者 X
+--------+ +-----------+ +--------+
| 订单 | -> | | -> | 库存 |
| 服务 | | 消息队列 | | 服务 |
+--------+ | | +--------+
| (Kafka/ |
生产者 B | RabbitMQ)| 消费者 Y
+--------+ | | +--------+
| 支付 | -> | | -> | 通知 |
| 服务 | +-----------+ | 服务 |
+--------+ +--------+
优势:组件解耦、易于扩展、削峰填谷。劣势:调试困难、消息可能丢失或重复、增加系统复杂度。
六、从理论到实践:设计一个 URL 缩短服务
6.1 需求分析
为了将前面的方法论落地,我们以一个经典的系统设计题——URL 缩短服务来做一次完整练习。
功能需求:
- 用户输入一个长 URL,系统返回一个短 URL
- 用户访问短 URL 时,系统重定向到对应的长 URL
- 短链有效期 5 年
非功能需求:
- 每天生成 1 亿个短链
- 读写比 100:1
- 重定向延迟 < 50ms
- 可用性 99.99%
6.2 容量估算
写入 QPS = 1 亿 / 86400 ≈ 1200 QPS
峰值写入 QPS ≈ 3600 QPS
读取 QPS = 1200 x 100 = 120000 QPS
峰值读取 QPS ≈ 360000 QPS
5 年短链总量 = 1 亿 x 365 x 5 = 1825 亿 ≈ 2000 亿条
每条记录大小(短码 + 长 URL + 时间戳)≈ 500 字节
总存储 = 2000 亿 x 500 字节 = 100 TB
6.3 概要设计
+-------------------------------------------+
| 客户端 / 浏览器 |
+-------------------------------------------+
|
+-------------+
| 负载均衡器 |
+-------------+
/ \
+----------+ +----------+
| API 服务1| | API 服务2| (可水平扩展)
+----------+ +----------+
| |
+---------+---------+--------+---------+
| | |
+----------+ +---------+ +----------+
| 短码生成 | | 缓存层 | | 数据库 |
| 服务 | | Redis | | (分片) |
+----------+ +---------+ +----------+
6.4 关键设计决策
短码生成方案:使用 Base62 编码(a-z, A-Z, 0-9),7 位短码可以表示 62^7 ≈ 3.5 万亿种组合,远超 2000 亿的需求。
存储方案:使用 NoSQL(如 DynamoDB 或 Cassandra),因为数据结构简单、读写量大、不需要复杂查询。
缓存策略:热门短链缓存到 Redis,缓存命中率预计可达 80% 以上,将大部分读请求拦截在缓存层。
这个案例虽然简单,但完整展示了系统设计的四步法。后续章节中,我们将对其中涉及的每一项技术做深入探讨。
思考题
-
如果上述 URL 缩短服务要支持短链自定义(用户指定短码),系统设计上需要做哪些调整?需要考虑哪些冲突和并发问题?
-
在容量估算中,我们使用了”峰值系数 3”来估算峰值 QPS。在你的实际工作中,不同类型的业务系统(如电商、社交、工具类),峰值系数通常是多少?影响峰值系数的因素有哪些?
-
有人说”架构设计就是做选择题”,你同意吗?请结合自己的经历举出一个你做过的架构取舍决策。
结尾自测
完成本章学习后,请尝试回答以下五个问题:
-
系统设计的四步法分别是什么?每一步的核心产出是什么?
- 答:明确需求与约束(需求文档)、粗略估算(量级数据)、概要设计(系统拓扑图)、深入设计与权衡(详细方案与 Trade-off 分析)。
-
DAU 为 2000 万的社交应用,若每用户日均产生 10 次 API 调用,峰值系数为 5,峰值 QPS 约为多少?
- 答:平均 QPS = 2000 万 x 10 / 86400 ≈ 2300,峰值 QPS ≈ 2300 x 5 = 11500 QPS。
-
“四个 9”的可用性意味着年停机时间不超过多少?
- 答:52.6 分钟。
-
垂直扩展和水平扩展的核心区别是什么?为什么现代互联网系统更倾向于水平扩展?
- 答:垂直扩展是增强单机性能,水平扩展是增加机器数量。水平扩展没有物理上限,且可以按需线性扩容,更适合互联网业务的弹性需求。
-
为什么要关注 P99 延迟而不仅仅是平均延迟?
- 答:平均延迟会掩盖长尾问题,P99 反映的是绝大多数用户的真实体验,而那 1% 的慢请求背后往往是系统的瓶颈点,可能影响到最重要的用户群体。
下一章预告:了解了系统设计的基础方法论之后,我们将深入高性能架构的世界,从缓存策略到数据库优化,一步步打造能够承载海量流量的系统。
购买课程解锁全部内容
面试晋升必学:11 章掌握系统设计
¥29.90