系列 · 系统设计 · 第 1 篇

系统设计(一):以系统思维思考——负载、延迟与估算的艺术

掌握系统设计的基础能力:快速估算(back-of-envelope estimation)、可用性数学推导、SLA 定义,以及一套可复用的系统设计框架。

一位朋友曾请我帮忙排查一个性能问题。他们的照片分享应用在开发环境运行良好,但一到生产环境就彻底崩溃:数据库过载、API 网关频繁超时,用户大量看到 504 错误。当我问系统每秒处理多少请求时,对方回答“我不知道”;再问预期负载是多少,他说“根本没想过这个问题”。

这次对话恰恰点出了系统设计的核心意义——它远不止是在白板上画几个方框和箭头,而是构建一套心智模型,让你能在系统崩溃前就预判其行为。


系统设计究竟是什么?#

系统设计是为满足特定需求而定义系统架构、组件、模块、接口与数据流的过程。但这个教科书式的定义其实偏离了重点。实际上,系统设计是一门在不确定性中做出明智权衡的学科。

每个程序员都应该知道的延迟数据

每个系统设计决策都涉及权衡:

  • 更多缓存意味着更低延迟,但一致性更难保证
  • 更多副本带来更高可用性,却也增加了运维复杂度
  • 更多微服务支持独立部署,但也引入分布式系统的典型难题
  • 更强的一致性保障会带来更高延迟和更低吞吐量
  • 更多数据反规范化能加速读取,但会让写入逻辑变得更复杂

目标从来不是追求完美,而是在真正落地前,就清晰理解每个选择可能带来的后果。

系统设计远不止于面试#

人们常把系统设计当作一种面试技巧,但这大大低估了它的价值。同样的思维方式,其实每天都在真实工程场景中发挥作用:

  • 为新服务在 SQL 和 NoSQL 数据库之间做选型
  • 决定是加一层缓存,还是直接扩容数据库
  • 评估系统能否扛住营销活动带来的 10 倍流量激增
  • 设计从单体架构迁移到微服务的可行路径
  • 排查由级联故障引发的线上事故

初级工程师和高级工程师的差距,往往不在于编码能力,而在于对大规模系统的推理能力。初级工程师实现一个功能;高级工程师则会追问:“当 10,000 个用户同时触发这个功能时会发生什么?当下游服务变慢时怎么办?当数据库磁盘写满时系统如何表现?”

核心原则#

在深入估算技巧之前,先明确支撑所有优秀系统设计的三大基石:

可用性 9 个 9 与停机时间

可扩展性(Scalability):系统能够平滑应对增长——更多用户、更多数据、更多请求——而无需彻底重构。可扩展性分为垂直扩展(升级单机配置)和水平扩展(增加机器数量)。实践中几乎总是优先选择水平扩展,因为它没有理论上限,还能天然提供冗余。

可靠性(Reliability):即使出现硬件故障、软件缺陷、人为误操作或流量突增,系统仍能持续正确运行。可靠性通过冗余设计、故障隔离和优雅降级来实现。

可维护性(Maintainability):系统能被团队长期理解、修改和运维。这意味着清晰的抽象边界、完善的监控体系、简单的部署流程和准确的文档。一个运行完美却无法在出问题时快速定位和修复的系统,绝不能算作好设计。

这三者常常相互冲突:高度可扩展的系统可能更难维护(组件太多);高度可靠的系统可能牺牲部分性能(例如共识协议会引入额外延迟)。系统设计的艺术,就在于为你的具体场景找到恰当的平衡点。

快速估算(Back-of-Envelope Estimation)#

可用性 9 个 9:作为可靠性堡垒的运行时间保证

系统设计中最宝贵的一项技能,就是能快速、粗略地估算关键指标的数量级。你不需要精确数字,只需要数量级上的正确性。误差在 2 倍以内完全可以接受;但如果差了 100 倍,那说明你的架构方向已经错了。

容量估算工作流程

2 的幂次(Powers of 2)#

务必牢记这些数值,它们在估算存储、内存和网络容量时会反复出现。

2的幂快速参考

幂次精确值近似值
2^101,0241 千(1 KB)
2^201,048,5761 百万(1 MB)
2^301,073,741,8241 十亿(1 GB)
2^40~1.1 万亿1 万亿(1 TB)
2^50~1.1 千万亿1 拍字节(1 PB)

每位程序员都应熟记的延迟数字#

这些数值最初由 Jeff Dean 整理,并随硬件演进不断更新,构成了性能推理的基石。具体数值虽会随代际变化,但相对量级却异常稳定。

操作延迟备注
L1 缓存访问0.5 ns片上缓存,近乎免费
L2 缓存访问7 ns仍在芯片上,约为 L1 的 14 倍
主内存访问100 ns芯片外但本地,速度尚可
SSD 随机读16 μs约为主存的 150 倍
HDD 随机读2 ms约为主存的 20,000 倍
1 KB 数据经 1 Gbps 网络传输10 μs小载荷下网络极快
1 MB 数据顺序读内存250 μs内存带宽很高
1 MB 数据顺序读 SSD1 msSSD 顺序读表现优异
1 MB 数据顺序读 HDD20 msHDD 顺序读尚可接受
同数据中心往返500 μs网络跳转成本很低
跨洲往返150 ms光速成为瓶颈

这张表的关键启示:

  • 内存随机访问比磁盘快 100–1000 倍
  • 所有存储介质上,顺序访问都远快于随机访问
  • 数据中心内部的网络往返延迟极低(约 0.5 ms)
  • 跨地域网络调用代价高昂(约 150 ms)
  • 将热点数据缓存在内存中,能有效规避最昂贵的操作

时间单位换算#

估算时随时调用:

1
2
3
1 天    = 86,400 秒   ≈ 100,000 秒(10^5)
1 月    = 2,592,000 秒 ≈ 2.5 百万秒(2.5 × 10^6)
1 年    = 31,536,000 秒 ≈ 30 百万秒(3 × 10^7)

存储估算#

以下是估算存储需求的标准流程。

系统设计框架

步骤 1:确定日活跃用户数(DAU)

从产品假设出发。例如,一款新的照片分享 App 可假设 DAU 为 1000 万。

步骤 2:估算每位用户每日操作数

每位用户平均每天上传 2 张照片,浏览 50 张照片。

步骤 3:计算每秒请求数(QPS)

1
2
3
4
5
6
7
写 QPS:
  1000 万用户 × 2 次上传/天 = 2000 万次上传/天
  2000 万 / 100,000 秒/天 = 200 次写入/秒

读 QPS:
  1000 万用户 × 50 次浏览/天 = 5 亿次浏览/天
  5 亿 / 100,000 秒/天 = 5,000 次读取/秒

步骤 4:估算单条数据的存储大小

一张照片通常以多种尺寸存储:

  • 原图:2 MB
  • 中等尺寸:200 KB
  • 缩略图:20 KB
  • 元数据(JSON):1 KB
  • 每张照片总计:约 2.2 MB

步骤 5:计算存储增长量

1
2
3
日增长:   2000 万张 × 2.2 MB = 44 TB/天  
月增长:   44 TB × 30 = 1.3 PB/月  
年增长:   44 TB × 365 = 16 PB/年  

步骤 6:规划数据保留与副本策略

如果照片永久保存,并为持久性做 3 副本复制:

  • 第一年:16 PB × 3 = 48 PB 原始存储

至此你已明确:必须使用分布式对象存储系统,而不是单台数据库服务器。仅凭这一估算,就避免了一个根本性的架构错误。

带宽估算#

带宽估算直接源于你的 QPS 和载荷大小。

1
2
3
4
5
写入带宽:
  200 次/秒 × 2.2 MB = 440 MB/秒 = 3.5 Gbps

读取带宽:
  5,000 次/秒 × 200 KB(取中等尺寸) = 1 GB/秒 = 8 Gbps

这些数字告诉你:

  • 你必须使用 CDN。单台源站服务器根本无法承载 8 Gbps 的图片流量。
  • 写入入口需要高吞吐的分布式存储支持。
  • 峰值流量(通常是平均值的 2–5 倍)可能将读取带宽推高至 20–40 Gbps。

网络容量速查表#

估算带宽时,了解常见基础设施的典型吞吐能力很有帮助:

组件典型吞吐量
单网卡(1 GbE)~100 MB/秒
单网卡(10 GbE)~1 GB/秒
单网卡(25 GbE)~2.5 GB/秒
单 SSD(NVMe)~3 GB/秒(顺序读)
单 HDD~200 MB/秒(顺序读)
Redis 单实例~100K ops/秒,~1 GB/秒
PostgreSQL 单实例~5K–20K QPS(取决于查询复杂度)
Kafka 单 Broker~100 MB/秒/分区

一旦估算出的带宽超出单个组件的承载能力,就必须将负载分散到多个实例上。这正是估算的核心价值:它在你动手构建之前,就明确告诉你哪些地方需要水平扩展。

内存估算#

内存估算决定了你能将多少数据保留在 RAM 中,直接影响缓存策略和成本。

80/20 法则(帕累托原理)#

在大多数系统中,20% 的数据贡献了 80% 的请求。将这部分“热数据”缓存在内存中,就能满足 80% 的流量,而无需访问数据库。

1
2
3
4
5
6
7
8
9
总数据量:10 TB  
热数据集(20%):2 TB  
内存成本:~$10/GB/月(云厂商定价)  
缓存热数据成本:2,000 GB × $10 = $20,000/月  

节省的数据库读取成本(粗略估算):
  无缓存:100,000 QPS × $0.001/次 = $100/秒 = $8.6M/月  
  有缓存(80% 命中率):20,000 QPS 访问 DB = $1.7M/月  
  节省:约 $6.9M/月  

以上数字仅为示意,但足以说明:缓存是系统设计中最具成本效益的优化手段之一

实用内存限制参考#

机器类型内存典型用途
标准云实例(r6g.xlarge)32 GB小型缓存、单服务部署
大型云实例(r6g.4xlarge)128 GB中型缓存、Redis 节点
超大型(r6g.16xlarge)512 GB大型内存数据库
Redis 集群(10 节点)320 GB – 5 TB分布式缓存
内存数据库(SAP HANA)最高 24 TB企业级分析

当你的缓存内存需求超出单机承载能力时,就需要引入分布式缓存(如 Redis Cluster 或 Memcached)。如果连集群都难以合理管理,那就得重新思考缓存策略——比如只缓存元数据,而非完整对象。

SLA、SLO 与 SLI#

这三个术语经常被混淆,但它们有明确区别。

SLI(Service Level Indicator,服务等级指标):对服务某一方面的量化度量。例如:

  • 请求延迟(p50、p95、p99)
  • 错误率(5xx 响应占比)
  • 吞吐量(每秒请求数)
  • 可用性(服务正常运行时间占比)

SLO(Service Level Objective,服务等级目标):为某个 SLI 设定的目标值或范围。例如:

  • p99 延迟 < 200ms
  • 错误率 < 0.1%
  • 可用性 > 99.9%

SLA(Service Level Agreement,服务等级协议):服务提供方与客户之间的合同,明确规定 SLO 及未达标时的补救措施。例如:

  • “若月度可用性低于 99.9%,客户可获得 10% 的服务抵扣”
  • “若 p99 延迟连续超过 500ms 达 1 小时,则触发事故响应流程”

三者关系层层递进:SLI 是测量值,SLO 是 SLI 的目标,SLA 则将 SLO 正式化为具有约束力的契约。

可用性数学#

可用性以给定周期内正常运行时间的百分比表示,业界习惯用“nines”(几个 9)来简称。

可用性年停机时间月停机时间周停机时间
99%(两个 9)3.65 天7.31 小时1.68 小时
99.9%(三个 9)8.77 小时43.83 分钟10.08 分钟
99.95%4.38 小时21.92 分钟5.04 分钟
99.99%(四个 9)52.60 分钟4.38 分钟1.01 分钟
99.999%(五个 9)5.26 分钟26.30 秒6.05 秒

串行 vs 并行可用性#

当组件串联(所有组件必须正常,系统才可用)时:

1
2
3
4
整体可用性 = A1 × A2 × A3 × ...

示例:Web 服务器(99.9%)→ 应用服务器(99.9%)→ 数据库(99.9%)  
整体 = 0.999 × 0.999 × 0.999 = 0.997 = 99.7%

即使每个组件都达到三个 9 的可用性,整体系统仍不足三个 9。这解释了为何链路中的每个环节都至关重要。

当组件并联(任一组件正常,系统即可用)时:

1
2
3
4
整体可用性 = 1 − (1 − A1) × (1 − A2)

示例:两个数据库副本,各自可用性为 99.9%  
整体 = 1 − (0.001 × 0.001) = 1 − 0.000001 = 99.9999%

冗余能极大提升整体可用性。两个三个 9 的组件并联,组合后可达六个 9。这正是复制(replication)成为可靠系统基石的原因。

“Nines”的成本#

每增加一个 9,成本大约增加 10 倍。从 99.9% 提升到 99.99%,并非只是提升 0.09%;而是将年允许停机时间从 8.77 小时压缩到 52.6 分钟。这要求:

  • 自动故障转移(无需人工介入)
  • 多地域部署(抵御整个数据中心的故障)
  • 全面的监控与告警体系
  • 自动回滚机制(应对错误发布)
  • 混沌工程(主动暴露潜在故障模式)

大多数应用应瞄准 99.9% 至 99.95% 的可用性。四个 9 及以上,通常只用于支付系统、核心数据库等关键基础设施。

容量规划(Capacity Planning)#

信封背面估算:白板上的粗略计算

容量规划是确定生产环境所需资源以应对动态需求的过程。以下是几个关键概念。

峰值 vs 平均值#

生产系统必须能应对峰值流量,而非平均流量。不同应用类型的峰值/平均比差异很大:

应用类型峰值/平均比
电商(常规)2–3x
电商(黑色星期五)5–10x
社交媒体2–4x
企业级 SaaS1.5–2x
游戏3–5x

预留余量(Headroom)#

切勿让系统满负荷运行。行业标准是预留 30–50% 的余量:

1
2
3
4
5
6
所需容量 = 峰值负载 / (1 − 余量百分比)

示例:
  峰值 QPS = 10,000  
  余量 = 30%  
  所需容量 = 10,000 / 0.7 = 14,286 QPS  

突发流量应对#

即使预留了余量,突发流量仍可能超出规划容量。常用策略包括:

  • 自动扩缩容(Auto-scaling):基于负载指标动态增减实例(适用于无状态服务)
  • 限流(Rate limiting):优雅拒绝超额请求(返回 429 而非直接崩溃)
  • 队列式负载均衡(Queue-based load leveling):将请求暂存队列,按可持续速率处理
  • 熔断器(Circuit breakers):当下游服务过载时快速失败,防止雪崩效应

系统设计框架#

无论是在面试还是真实架构讨论中,结构化方法都能帮你避免迷失方向。以下是一套可复用的框架。

步骤 1:明确需求(5 分钟)#

区分功能性需求(系统做什么)和非功能性需求(做得有多好)。

功能性:

  • 核心功能有哪些?
  • 用户是谁?
  • 输入与输出是什么?

非功能性:

  • 预期规模(用户数、QPS、数据量)?
  • 延迟要求?
  • 所需可用性等级?
  • 可接受的一致性模型?

步骤 2:快速估算(5 分钟)#

计算:

  • QPS(读/写分开)
  • 存储需求(日/月/年)
  • 带宽需求
  • 内存需求(用于缓存)

步骤 3:高层设计(10 分钟)#

勾勒主要组件:

  • 客户端层
  • API 层(端点、协议)
  • 应用层(业务逻辑)
  • 数据层(数据库、缓存、对象存储)
  • 支撑设施(消息队列、搜索、CDN)

并端到端描述主用例的数据流。

步骤 4:深度剖析(15 分钟)#

选取 2–3 个关键组件深入探讨:

  • 数据库 Schema 设计
  • 缓存策略
  • 数据分片(Partitioning)
  • 复制与一致性
  • API 接口设计细节

步骤 5:识别瓶颈与改进点(5 分钟)#

  • 单点故障及其消除方案
  • 性能瓶颈及优化路径
  • 监控与告警策略
  • 未来扩展考量

真实案例:照片分享 App 的需求估算#

我们以类似 Instagram 的照片分享应用为例,走一遍完整的估算流程。

需求#

功能性:

  • 上传照片
  • 查看关注用户的动态流(feed)
  • 关注/取消关注用户
  • 点赞与评论照片

非功能性:

  • 总用户数 5 亿,日活用户(DAU)1 亿
  • 用户平均每天上传 1 张照片
  • 用户平均每天查看动态流 10 次,每次看到约 20 张照片
  • 照片加载时间 ≤ 200ms
  • 可用性:99.9%
  • 动态流更新可接受最终一致性

估算#

写 QPS(上传)

1
2
3
1 亿 DAU × 1 次上传/天 = 1 亿次上传/天  
1 亿 / 86,400 ≈ 1,200 次上传/秒  
峰值(3x):≈ 3,600 次上传/秒  

读 QPS(动态流浏览)

1
2
3
1 亿 DAU × 10 次浏览/天 × 20 张照片/次 = 200 亿次照片读取/天  
200 亿 / 86,400 ≈ 230,000 次读取/秒  
峰值(3x):≈ 700,000 次读取/秒  

这是一个典型的读多写少系统,读写比约为 200:1。

存储

1
2
3
4
5
6
7
8
9
照片尺寸:
  原图:2 MB  
  展示图:500 KB  
  缩略图:50 KB  
  每张照片总计:≈ 2.5 MB  

日存储增长:1 亿 × 2.5 MB = 250 TB/天  
年存储增长:250 TB × 365 ≈ 91 PB/年  
3 副本后:≈ 273 PB/年  

带宽

1
2
写入:1,200 次/秒 × 2.5 MB = 3 GB/秒 = 24 Gbps  
读取:230,000 次/秒 × 500 KB = 115 GB/秒 = 920 Gbps  

920 Gbps 的读取带宽极为庞大。这确认我们必须:

  • 使用多地域 CDN,在边缘节点就近服务图片
  • 对热门照片进行激进缓存
  • 构建多层缓存(CDN、应用层、数据库层)

缓存内存: 依据 80/20 法则(20% 的照片产生 80% 的流量),我们缓存热数据集:

1
2
3
4
日唯一浏览照片数:≈ 10 亿(粗略估计)  
热数据集(20%):2 亿张照片  
每张缓存(元数据 + 缩略图 URL):1 KB  
缓存内存总量:2 亿 × 1 KB = 200 GB  

200 GB 缓存可在 Redis 集群中轻松实现(例如 10 台机器,每台 32 GB 内存,预留 Redis 开销余量)。

架构概要#

基于上述估算,系统需包含:

  1. 对象存储(S3/GCS)存放照片文件 —— 任何关系型数据库都无法承载 91 PB/年的增长
  2. CDN 支撑读取路径 —— 920 Gbps 流量无法由源站服务器直接提供
  3. 分布式缓存(Redis 集群)缓存照片元数据 —— 200 GB 热数据集
  4. 关系型数据库 存储用户数据、关注关系、点赞记录 —— 数据量相对较小
  5. 消息队列 处理异步任务 —— 动态流生成、通知推送、图片处理
  6. 搜索/索引服务 支撑发现功能

估算驱动我们走向了正确的架构,全程未画一张图。这正是快速估算(back-of-envelope math)的力量。

常见估算错误#

忽略峰值:平均 QPS 对容量规划毫无意义。务必乘以 2–5 倍作为峰值系数。

忽视读写比:多数系统读远多于写(100:1 或更高)。该比率直接决定你的缓存策略、复制拓扑与数据库选型。

假设线性增长:用户基数与数据量极少线性增长。应为未来 3–5 年的指数级增长做好准备。

混淆存储与带宽:100 TB 的静态存储 ≠ 100 TB 的带宽需求。带宽取决于访问模式,而非总数据量。

忘记副本开销:若为持久性做 3 副本,实际存储成本是你估算值的 3 倍。

追求精确数字而非数量级:估算的目的,是判断你需要 1 台服务器还是 100 台,而非 47 台还是 53 台。大胆四舍五入,善用 10 的幂次。

估算速查表#

以下是常见系统设计估算的快速参考。将这些数字印在脑中,助你快速推理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
日活用户(DAU)→ QPS:
  QPS ≈ DAU × (单用户日操作数) / 100,000

年存储量:
  存储 ≈ DAU × (单次操作数据量) × (单用户日操作数) × 365

常见读写比:
  社交媒体:  100:1 至 1000:1  
  电商平台:  10:1 至 100:1  
  即时通讯:  1:1 至 5:1  

缓存大小:
  缓存 ≈ 高频访问数据的 20%

典型单机极限:
  Web 服务器:    1,000–10,000 并发连接  
  数据库:        5,000–20,000 QPS(简单查询)  
  Redis:         100,000+ ops/秒  
  Kafka Broker:  100,000+ messages/秒  

数据尺寸:
  UUID:          16 字节  
  时间戳:        8 字节  
  短字符串:      50–200 字节  
  URL:           100–500 字节  
  JSON 对象:     200–2000 字节  
  图片(压缩后):100 KB – 5 MB  
  视频(1 分钟,压缩后):5–50 MB  

一致性模型:预览#

在推理分布式系统时,你会遇到不同的一致性模型。此处仅作简要预览,因为这些概念将在本系列后续文章中反复出现:

强一致性(Strong consistency):一次写入完成后,所有后续读取均返回最新值。这是单数据库服务器配合 ACID 事务所能提供的保证。它易于推理,但在分布式系统中实现成本高昂。

最终一致性(Eventual consistency):一次写入完成后,读取可能暂时返回旧值,但最终所有读取都将返回最新值。这是大多数缓存层、DNS 传播及许多 NoSQL 数据库采用的模型。它以更高可用性与更低延迟为代价,换取更强的伸缩性。

因果一致性(Causal consistency):若操作 A 在因果上依赖于操作 B(例如,回复依赖于原始消息),则任何看到 A 的进程也必然看到 B。它比最终一致性更强,但弱于强一致性,是许多社交应用的理想折中点。

一致性模型的选择,是系统设计中最具决定性的决策之一,并直接关联 CAP 定理:在网络分区(network partition)发生时,分布式系统必须在一致性(Consistency)与可用性(Availability)之间做出取舍。绝大多数大规模系统对大部分操作选择可用性与最终一致性,仅在支付处理等关键路径上保留强一致性。

下一步#

掌握了估算技能后,下一篇文章将聚焦于每个 Web 请求的前三跳:DNS 解析、CDN 缓存与负载均衡。这些组件位于用户与你的应用服务器之间,它们的设计是否合理,直接决定了你的系统能否承载你刚刚估算出的巨大负载。

本系列

系统设计 8 篇

  1. 01 系统设计(一):以系统思维思考——负载、延迟与估算的艺术 当前
  2. 02 系统设计(二):DNS、CDN 与负载均衡——请求旅程的前三跳
  3. 03 系统设计(三):API 设计——REST、gRPC、GraphQL 及如何明智选型
  4. 04 系统设计(四):缓存——在哪里缓存、淘汰什么,以及缓存何时反而有害
  5. 05 系统设计(五):消息队列与事件驱动架构
  6. 06 系统设计(六):微服务 vs 单体架构——坦诚的权衡分析
  7. 07 系统设计(七):数据管道——批处理、流处理与 Lambda 架构
  8. 08 系统设计(八):案例分析 —— 网址缩短服务、实时聊天系统、新闻信息流

读有所得?

GitHub 关注我 → 新文周更

GitHub