主题
第 6 章:系统设计
📋 本章概述
系统设计是软件工程师的核心能力,涉及需求分析、架构设计、技术选型、扩展性和可靠性等多个维度。本章将学习系统设计的方法论和常见模式,通过实际案例掌握设计大规模系统的能力。
学习目标
完成本章后,你将能够:
- ✅ 进行完整的需求分析
- ✅ 设计系统架构
- ✅ 进行合理的技术选型
- ✅ 设计可扩展的系统
- ✅ 保证系统高可用性
- ✅ 进行系统性能估算
- ✅ 设计优秀的 API
前置知识
- ✅ 掌握微服务架构
- ✅ 理解数据库设计
- ✅ 了解网络和分布式系统
- ✅ 熟悉常见设计模式
1. 系统设计方法论
1.1 系统设计流程
标准流程(SNAKE 模型):
Scope(明确范围) - 5分钟
- 明确功能需求
- 确定非功能需求
- 定义边界条件
Numbers(估算数据) - 5分钟
- 用户规模
- QPS(每秒查询数)
- 存储需求
- 带宽需求
API(设计接口) - 5分钟
- 核心 API 端点
- 请求/响应格式
- 认证授权方式
Knowledge(技术选型) - 10分钟
- 数据库选择
- 缓存策略
- 消息队列
- 存储方案
Expand(深入设计) - 15分钟
- 详细架构图
- 数据流程
- 关键组件设计
- 扩展性方案
1.2 非功能需求
🏰 The Metaphor: The Giant vs The Army
- Vertical Scaling (垂直扩展/Scale Up): 就像养一个巨人。如果活干不完,就给巨人吃更多饭(加 CPU/RAM),让他长得更大。
- 优点: 简单,不需要改代码。
- 缺点: 巨人总有长不到的时候(硬件极限),而且巨人病了(单点故障),活就没人干了。
- Horizontal Scaling (水平扩展/Scale Out): 就像组建一支军队。如果活干不完,就招更多的普通士兵(加服务器)。
- 优点: 理论上无限扩展,死几个士兵不影响大局(高可用)。
- 缺点: 管理一支军队比养一个巨人难多了(分布式复杂性)。
可扩展性(Scalability):
- 水平扩展 vs 垂直扩展
- 无状态设计
- 数据分片
高可用性(Availability):
- 冗余设计
- 故障转移
- 负载均衡
- 降级策略
一致性(Consistency):
- 强一致性 vs 最终一致性
- CAP 理论
- 分布式事务
性能(Performance):
- 响应时间
- 吞吐量
- 资源利用率
可靠性(Reliability):
- 数据持久化
- 备份恢复
- 容错机制
2. 容量规划和估算
2.1 估算方法
常用换算:
1 Million = 10^6 = 1,000,000
1 Billion = 10^9 = 1,000,000,000
1 KB = 10^3 bytes = 1,000 bytes
1 MB = 10^6 bytes
1 GB = 10^9 bytes
1 TB = 10^12 bytes
1 天 = 86,400 秒 ≈ 100,000 秒示例:Twitter 估算
假设:
- 日活用户:200M
- 每用户每天发 2 条推文
- 平均推文大小:300 bytes
- 读写比:100:1
计算:
写入 QPS = 200M * 2 / 86400 ≈ 4,600 QPS
峰值写入 QPS = 4,600 * 2 = 9,200 QPS
读取 QPS = 4,600 * 100 = 460,000 QPS
峰值读取 QPS = 920,000 QPS
每日存储 = 200M * 2 * 300 bytes = 120 GB/天
年存储 = 120 GB * 365 ≈ 44 TB/年(不含媒体)2.2 性能目标
响应时间:
- Web 页面加载:< 1s
- API 响应:< 100ms
- 数据库查询:< 10ms
可用性:
- 99.9%(three nines):每年 8.76 小时停机
- 99.99%(four nines):每年 52.6 分钟停机
- 99.999%(five nines):每年 5.26 分钟停机
3. 架构设计模式
3.1 分层架构
经典三层架构:
表示层(Presentation Layer)
↓
业务逻辑层(Business Logic Layer)
↓
数据访问层(Data Access Layer)
↓
数据库(Database)优势:
- 关注点分离
- 易于维护
- 可替换性
适用场景:
- 传统 Web 应用
- 企业级应用
3.2 微服务架构
特点:
- 服务独立部署
- 去中心化治理
- 按业务能力组织
挑战:
- 分布式复杂性
- 数据一致性
- 服务治理
3.3 事件驱动架构
核心概念:
- 事件生产者
- 事件消费者
- 消息队列
优势:
- 松耦合
- 异步处理
- 易于扩展
适用场景:
- 实时数据处理
- 异步工作流
- 微服务通信
4. 数据存储设计
4.1 数据库选择
关系型数据库(PostgreSQL, MySQL):
- ✅ 适合:结构化数据、事务要求高
- ❌ 不适合:海量数据、高并发写入
NoSQL 数据库:
| 类型 | 代表 | 适用场景 |
|---|---|---|
| 文档 | MongoDB | 半结构化数据、快速迭代 |
| 键值 | Redis, DynamoDB | 缓存、会话存储 |
| 列族 | Cassandra, HBase | 时序数据、大规模写入 |
| 图 | Neo4j | 社交网络、推荐系统 |
4.2 数据分片(Sharding)
📚 The Metaphor: The Library Section 数据库分片就像是图书馆的分区。 如果所有的书都堆在一个房间里,找书的人会挤破头。 于是你把书按首字母分类:
- A-M 的书放在东馆(Shard 1)。
- N-Z 的书放在西馆(Shard 2)。
读者(查询)进门前,先看一眼索引(Routing),就知道该去哪个馆。 这样,两个馆可以同时接待读者,吞吐量翻倍。
🏛️ Scaling Patterns: Consistent Hashing & CAP Theorem
- Consistent Hashing (一致性哈希): 解决分片扩容/缩容时的数据迁移问题。
- 将数据和节点都映射到一个哈希环上。
- 增加节点只影响相邻节点的数据,而不是全量重哈希 (Reshuffling)。
- CAP Theorem: 在分布式系统中,Consistency (一致性), Availability (可用性), Partition Tolerance (分区容错性) 三者只能得其二。
- CP: 强一致性 (Zookeeper, HBase)。网络分区时,拒绝服务。
- AP: 高可用性 (Cassandra, DynamoDB)。网络分区时,返回旧数据 (最终一致性)。
- P 是必须的 (网络必然会分区)。所以只能在 C 和 A 之间权衡。
分片策略:
1. 范围分片:
- 按 ID 范围:1-1000 → Shard1, 1001-2000 → Shard2
- 优点:简单
- 缺点:数据分布可能不均
2. 哈希分片:
- 按 ID 哈希:hash(user_id) % N
- 优点:数据分布均匀
- 缺点:难以范围查询
3. 地理分片:
- 按地区:中国用户 → Shard-CN, 美国用户 → Shard-US
- 优点:就近访问,低延迟
- 缺点:跨区域查询复杂
4. 一致性哈希:
- 解决节点增减时的数据迁移问题
- 虚拟节点平衡负载
5. 缓存策略
5.1 缓存模式
🧠 The Metaphor: The Short-term Memory 缓存就像是你的短期记忆。 数据库是书本。
- Cache-Aside: 老师问你问题。你先想一下(查缓存)。想不起来,就去翻书(查数据库),然后背下来(写入缓存),最后回答老师。
- Write-Through: 你做笔记时,一边写在草稿纸上(缓存),一边立刻写在笔记本上(数据库)。很安全,但写得慢。
- Write-Behind: 你先狂写在草稿纸上(缓存)。等下课了,再慢慢抄到笔记本上(数据库)。写得飞快,但如果草稿纸丢了(断电),笔记就没了。
Cache-Aside(旁路缓存):
读取:
1. 先查缓存
2. 缓存未命中,查数据库
3. 写入缓存
4. 返回数据
更新:
1. 更新数据库
2. 删除缓存Read-Through:
- 缓存层自动加载数据
- 应用层不感知
Write-Through:
- 同时写入缓存和数据库
- 保证一致性
Write-Behind:
- 先写缓存,异步写数据库
- 性能好,但可能丢数据
5.2 缓存失效策略
常见问题:
缓存穿透: 查询不存在的数据
- 解决:布隆过滤器、缓存空值
缓存击穿: 热点数据过期
- 解决:永不过期、互斥锁
缓存雪崩: 大量缓存同时过期
- 解决:随机过期时间、多级缓存
6. API 设计
6.1 RESTful API 设计
资源命名:
✅ GET /users # 获取用户列表
✅ GET /users/123 # 获取单个用户
✅ POST /users # 创建用户
✅ PUT /users/123 # 更新用户(全量)
✅ PATCH /users/123 # 更新用户(部分)
✅ DELETE /users/123 # 删除用户
❌ GET /getUsers
❌ POST /createUser
❌ POST /users/delete/123版本控制:
方式 1:URL 路径
GET /api/v1/users
方式 2:请求头
GET /api/users
Accept: application/vnd.myapi.v1+json
方式 3:查询参数
GET /api/users?version=1分页:
GET /users?page=1&limit=20
响应:
{
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"total": 1000,
"totalPages": 50
}
}6.2 GraphQL
优势:
- 客户端控制数据结构
- 单个请求获取多个资源
- 强类型 Schema
劣势:
- 学习曲线陡峭
- 缓存复杂
- 性能优化困难
7. 系统设计案例
案例 1:设计 URL 缩短服务
需求:
- 长 URL 转短 URL
- 短 URL 跳转到长 URL
- 支持自定义短链
- 统计访问次数
估算:
- 每日新增:100M 短链
- QPS:1,000(写),10,000(读)
- 存储:100M * 365 * 100 bytes ≈ 3.65 TB/年
设计:
组件:
- API 服务器(负载均衡)
- 数据库(分片存储)
- 缓存(Redis)
- 计数器(Redis)
短 URL 生成:
方式 1:哈希(MD5/SHA256)+ 截取
方式 2:自增 ID + Base62 编码
方式 3:随机字符串 + 碰撞检测
数据库设计:
urls 表:
- id (主键)
- short_code (唯一索引)
- long_url
- user_id
- created_at
- expires_at
访问统计:
- Redis 计数器
- 异步写入数据库案例 2:设计实时排行榜
需求:
- 实时更新用户分数
- 查询前 100 名
- 查询用户排名
技术选型:
- Redis Sorted Set
- ZADD: 添加/更新分数
- ZREVRANGE: 获取排名前 N
- ZREVRANK: 获取用户排名
优化:
- 分段排行榜(按时间、地区)
- 预计算(定时更新)
- 缓存热门数据
案例 3:设计分布式限流器
算法选择:
固定窗口: 简单但有突刺问题 滑动窗口: 平滑但实现复杂 令牌桶: 允许突发流量 漏桶: 匀速处理请求
实现: Redis + Lua 脚本保证原子性
8. 实战练习
练习 1:设计 Instagram
要求:
- 用户可以上传照片
- 用户可以关注其他用户
- Feed 流显示关注用户的照片
- 点赞和评论功能
考虑点:
- 图片存储(CDN)
- Feed 生成(推模式 vs 拉模式)
- 数据库设计
- 缓存策略
练习 2:设计在线聊天系统
要求:
- 一对一聊天
- 群组聊天
- 在线状态
- 消息持久化
考虑点:
- WebSocket vs 长轮询
- 消息队列
- 未读消息计数
- 消息存储
练习 3:设计搜索自动完成
要求:
- 用户输入时实时提示
- 支持拼音搜索
- 支持热门搜索
考虑点:
- Trie 树
- 缓存策略
- 实时更新
- 个性化推荐
9. 延伸阅读
推荐书籍
- 《系统设计面试:内幕指南》
- 《设计数据密集型应用》
- 《大规模分布式存储系统》
- 《微服务设计》
在线资源
实践平台
- LeetCode System Design
- Pramp
- Exponent
下一章:电商平台项目实战