# 系统设计方法论

::: tip 前言
**系统设计不是拍脑袋画架构图，而是一套有章可循的方法论。** 无论是面试中的系统设计题，还是实际工作中的架构设计，都遵循相似的思考框架：先搞清楚问题，再估算规模，然后设计方案，最后深入优化。
:::

**这篇文章会带你学什么？**

学完这章后，你将获得：

- **设计流程**：掌握系统设计的四步法框架
- **容量估算**：学会"信封背面估算"的技巧
- **常见模式**：熟悉缓存、分库分表、消息队列等核心模式
- **权衡思维**：理解架构设计中的 trade-off 思维
- **实战案例**：通过短链服务、Feed 流等案例理解设计过程

| 章节 | 内容 | 核心概念 |
|-----|------|---------|
| **第 1 章** | 设计四步法 | 需求澄清、容量估算、架构设计、深入优化 |
| **第 2 章** | 容量估算 | QPS、存储、带宽、信封背面估算 |
| **第 3 章** | 核心设计模式 | 缓存、分库分表、消息队列、CDN |
| **第 4 章** | 权衡思维 | 一致性 vs 可用性、性能 vs 成本 |
| **第 5 章** | 经典案例 | 短链服务、Feed 流、秒杀系统 |

---

## 1. 系统设计四步法

系统设计不是一上来就画架构图。无论是面试还是实战，都应该遵循一个结构化的流程。

<SystemDesignStepsDemo />

::: tip 为什么要先澄清需求？
很多人拿到题目就开始画图，结果设计了一个"正确但不是面试官想要的"系统。花 5 分钟问清楚需求，能避免后面 30 分钟的返工。

常见的澄清问题：
- 系统的核心功能是什么？（不要设计所有功能）
- 用户规模多大？（决定是否需要分布式）
- 读写比例？（决定缓存策略）
- 数据需要保留多久？（决定存储方案）
:::

---

## 2. 容量估算：信封背面的艺术

"信封背面估算"（Back-of-envelope estimation）是系统设计中的核心技能。不需要精确计算，只需要知道量级。

<CapacityEstimationDemo />

### 常用换算速查

| 量级 | 换算 | 记忆技巧 |
|------|------|---------|
| 1 天 | 86,400 秒 | ≈ 10 万秒 |
| 1 亿请求/天 | ≈ 1,200 QPS | 除以 10 万 |
| 1 KB × 1 亿 | ≈ 100 GB | 1 亿条小记录 |
| 1 MB × 100 万 | ≈ 1 TB | 100 万张图片 |

### 2-8 法则在估算中的应用

大多数系统遵循 80/20 法则：20% 的数据承载 80% 的请求。这意味着：

- **缓存大小** ≈ 总数据量 × 20%
- **热点 QPS** ≈ 总 QPS × 80% 集中在 20% 的 key 上
- **缓存命中率**目标 ≈ 80%+（低于这个值说明缓存策略有问题）

---

## 3. 核心设计模式

系统设计中反复出现的模式，掌握这些就能应对大多数场景。

### 3.1 缓存模式

| 模式 | 读路径 | 写路径 | 适用场景 |
|------|--------|--------|---------|
| Cache-Aside | 先查缓存，miss 则查 DB 并回填 | 先写 DB，再删缓存 | 通用场景，最常用 |
| Read-Through | 缓存层自动从 DB 加载 | 同 Cache-Aside | 需要缓存框架支持 |
| Write-Behind | 同 Cache-Aside | 先写缓存，异步写 DB | 写密集型，可容忍丢数据 |

::: tip 为什么是"删缓存"而不是"更新缓存"？
更新缓存在并发场景下容易出现数据不一致：线程 A 和 B 同时更新，A 先写 DB 但 B 先更新缓存，导致缓存中是 B 的旧值。删除缓存则让下次读请求重新从 DB 加载，天然避免这个问题。
:::

### 3.2 分库分表

当单表数据量超过千万级，或单库 QPS 超过瓶颈时，就需要考虑分库分表。

| 策略 | 做法 | 优点 | 缺点 |
|------|------|------|------|
| 垂直分库 | 按业务域拆分数据库 | 业务解耦，独立扩展 | 跨库 JOIN 困难 |
| 水平分表 | 同一张表按规则拆成多张 | 单表数据量可控 | 分片键选择关键 |
| 垂直分表 | 把大字段拆到独立表 | 减少 IO，提升查询效率 | 需要额外 JOIN |

**分片键选择原则**：
- 选择查询最频繁的字段（如 user_id）
- 数据分布要均匀，避免热点
- 尽量让同一用户的数据在同一分片（减少跨分片查询）

### 3.3 消息队列

消息队列是分布式系统的"减震器"，核心作用是解耦、异步、削峰。

| 场景 | 不用队列 | 用队列 |
|------|---------|--------|
| 下单后发通知 | 下单接口同步调用通知服务，通知失败导致下单失败 | 下单成功后发消息，通知服务异步消费 |
| 秒杀抢购 | 瞬间流量打爆数据库 | 请求先入队列，后端按能力消费 |
| 数据同步 | 服务 A 直接调用服务 B 的接口 | 服务 A 发事件，服务 B 订阅处理 |

---

## 4. 权衡思维：没有银弹

架构设计的本质是权衡（Trade-off）。每个决策都有代价，关键是理解代价并做出适合当前阶段的选择。

| 权衡维度 | 选项 A | 选项 B | 决策依据 |
|---------|--------|--------|---------|
| 一致性 vs 可用性 | 强一致（CP） | 高可用（AP） | 业务能否容忍短暂不一致？ |
| 性能 vs 成本 | 全量缓存 | 按需缓存 | 数据量和预算 |
| 简单 vs 灵活 | 单体架构 | 微服务 | 团队规模和业务复杂度 |
| 实时 vs 批量 | 流式处理 | 批处理 | 数据时效性要求 |
| 自建 vs 托管 | 自己搭 MySQL | 用云数据库 RDS | 运维能力和成本 |

::: tip 架构决策记录（ADR）
每个重要的架构决策都应该记录下来：**背景是什么、考虑了哪些方案、为什么选了这个、有什么代价**。这不是为了甩锅，而是为了让后来的人理解"为什么当时这么设计"。

格式很简单：
- **标题**：用 XXX 替代 YYY
- **背景**：我们遇到了什么问题
- **决策**：我们选择了什么方案
- **理由**：为什么选这个
- **代价**：这个决策的缺点和风险
:::

### 常见的错误权衡

| 错误 | 表现 | 正确做法 |
|------|------|---------|
| 过早优化 | 日活 1000 就上分库分表 | 先用单库，遇到瓶颈再拆 |
| 技术驱动 | "我想用 Kafka" 而不是 "我需要异步" | 从问题出发，而非从技术出发 |
| 忽略运维成本 | 选了最优方案但团队维护不了 | 方案要匹配团队能力 |
| 追求完美一致性 | 所有场景都用分布式事务 | 大多数场景最终一致性就够了 |

---

## 5. 经典案例

通过三个经典案例，把前面学到的方法论串起来。

### 5.1 短链服务（TinyURL）

短链服务是系统设计面试的经典题目，麻雀虽小五脏俱全。

**需求澄清**：
- 核心功能：长链接 → 短链接（写），短链接 → 重定向（读）
- 读写比：约 100:1（读远多于写）
- 日均重定向：1 亿次
- 短链永不过期

**容量估算**：

| 指标 | 计算 | 结果 |
|------|------|------|
| 写 QPS | 1 亿 / 100 / 86400 | ≈ 12 QPS |
| 读 QPS | 1 亿 / 86400 | ≈ 1,200 QPS |
| 峰值读 QPS | 1,200 × 3 | ≈ 3,600 QPS |
| 5 年存储 | 100 万/天 × 365 × 5 × 100B | ≈ 18 GB |
| 缓存（20%） | 18 GB × 20% | ≈ 3.6 GB |

**架构设计**：

```
写路径：客户端 → API Server → ID 生成器 → Base62 编码 → 写入 MySQL + Redis
读路径：客户端 → CDN → API Server → Redis 查询 → 302 重定向
                                    ↓ (cache miss)
                                  MySQL 查询 → 回填 Redis
```

**关键设计决策**：
- 短码生成：Snowflake 分布式 ID + Base62 编码，避免哈希冲突
- 缓存策略：Cache-Aside，热点短链用 CDN 加速
- 数据库：单表即可（18GB 很小），按短码做索引

### 5.2 Feed 流系统

社交平台的 Feed 流（朋友圈、微博首页）是另一个经典题目。

**核心挑战**：用户发一条动态，如何让所有关注者看到？

| 方案 | 做法 | 优点 | 缺点 |
|------|------|------|------|
| 拉模式（Pull） | 读取时实时聚合关注者的动态 | 写入简单，存储少 | 读取慢，关注多时延迟高 |
| 推模式（Push） | 发布时写入所有粉丝的收件箱 | 读取极快 | 大 V 发动态写扩散严重 |
| 推拉结合 | 普通用户推，大 V 拉 | 平衡读写性能 | 实现复杂 |

**推拉结合方案**：
- 粉丝数 < 1 万：发布时推送到所有粉丝的 Feed 缓存（推模式）
- 粉丝数 > 1 万：不推送，粉丝读取时实时拉取（拉模式）
- 用户打开 Feed 时：合并推送的内容 + 实时拉取大 V 的内容，按时间排序

### 5.3 秒杀系统

秒杀的核心挑战：瞬间超高并发 + 库存不能超卖。

**流量特征**：
- 活动开始前：大量用户刷新页面等待
- 活动开始瞬间：QPS 可能是平时的 100 倍以上
- 活动结束后：流量迅速回落

**分层削峰策略**：

```
用户请求 → CDN（静态页面）→ 网关（限流）→ 消息队列（削峰）→ 库存服务（扣减）
```

| 层级 | 策略 | 效果 |
|------|------|------|
| 前端 | 按钮置灰 + 随机延迟 + 验证码 | 过滤机器人，分散请求 |
| CDN | 静态资源缓存 | 减少 90% 的页面请求 |
| 网关 | 令牌桶限流 | 只放行系统能承受的流量 |
| 消息队列 | 请求入队，异步处理 | 削峰填谷，保护数据库 |
| 库存服务 | Redis 预扣减 + Lua 原子操作 | 防止超卖，毫秒级响应 |

::: tip 秒杀的核心原则
1. **尽量拦截在上游**：能在 CDN 挡住的就不要到应用层
2. **读写分离**：商品详情页走缓存，只有下单走数据库
3. **异步处理**：用户点击"抢购"后立即返回"排队中"，后台异步处理
4. **兜底方案**：限流、熔断、降级，任何一层出问题都有 Plan B
:::

---

## 总结

系统设计是一门实践性很强的技能，核心在于结构化思考和权衡取舍。

回顾本章的关键要点：

1. **四步法框架**：需求澄清 → 容量估算 → 架构设计 → 深入优化，每一步都不可跳过
2. **信封背面估算**：不需要精确，只需要知道量级，用于指导架构决策
3. **核心模式**：缓存、分库分表、消息队列、CDN、限流熔断——这些是系统设计的"积木"
4. **权衡思维**：没有完美方案，只有适合当前阶段的方案，记录每个决策的理由和代价
5. **经典案例**：短链服务练基础、Feed 流练推拉模型、秒杀练高并发——掌握这三个就能举一反三

## 延伸阅读

- [System Design Interview](https://www.amazon.com/System-Design-Interview-insiders-Second/dp/B08CMF2CQF) - Alex Xu 系统设计面试经典
- [Designing Data-Intensive Applications](https://dataintensive.net/) - Martin Kleppmann 数据密集型应用设计
- [The System Design Primer](https://github.com/donnemartin/system-design-primer) - GitHub 上最全的系统设计学习资源
- [ByteByteGo](https://bytebytego.com/) - Alex Xu 的系统设计可视化博客

