# 系统设计方法论 ::: tip 前言 **系统设计不是拍脑袋画架构图,而是一套有章可循的方法论。** 无论是面试中的系统设计题,还是实际工作中的架构设计,都遵循相似的思考框架:先搞清楚问题,再估算规模,然后设计方案,最后深入优化。 ::: **这篇文章会带你学什么?** 学完这章后,你将获得: - **设计流程**:掌握系统设计的四步法框架 - **容量估算**:学会"信封背面估算"的技巧 - **常见模式**:熟悉缓存、分库分表、消息队列等核心模式 - **权衡思维**:理解架构设计中的 trade-off 思维 - **实战案例**:通过短链服务、Feed 流等案例理解设计过程 | 章节 | 内容 | 核心概念 | |-----|------|---------| | **第 1 章** | 设计四步法 | 需求澄清、容量估算、架构设计、深入优化 | | **第 2 章** | 容量估算 | QPS、存储、带宽、信封背面估算 | | **第 3 章** | 核心设计模式 | 缓存、分库分表、消息队列、CDN | | **第 4 章** | 权衡思维 | 一致性 vs 可用性、性能 vs 成本 | | **第 5 章** | 经典案例 | 短链服务、Feed 流、秒杀系统 | --- ## 1. 系统设计四步法 系统设计不是一上来就画架构图。无论是面试还是实战,都应该遵循一个结构化的流程。 ::: tip 为什么要先澄清需求? 很多人拿到题目就开始画图,结果设计了一个"正确但不是面试官想要的"系统。花 5 分钟问清楚需求,能避免后面 30 分钟的返工。 常见的澄清问题: - 系统的核心功能是什么?(不要设计所有功能) - 用户规模多大?(决定是否需要分布式) - 读写比例?(决定缓存策略) - 数据需要保留多久?(决定存储方案) ::: --- ## 2. 容量估算:信封背面的艺术 "信封背面估算"(Back-of-envelope estimation)是系统设计中的核心技能。不需要精确计算,只需要知道量级。 ### 常用换算速查 | 量级 | 换算 | 记忆技巧 | |------|------|---------| | 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 的系统设计可视化博客