【系统设计(1)】系统设计入门方法论
文章目录
- 零、 系统设计四步法概览 (Overview)
- 1. 确定问题范围 (Understand & Scope) —— 3-10 分钟
- 2. 总体设计 (High-Level Design) —— 10-15 分钟
- 3. 细节设计 (Deep Dive) —— 10-25 分钟
- 4. 总结 (Wrap up) —— 3-5 分钟
- 第一步、理解问题与确定边界 (Understand & Scope)
- 1、提问与澄清:挖掘出功能与非功能需求
- 2、 估算规模:三个核心估算维度:流量、存储、带宽
- 第二步:高层架构设计 (High-Level Design)
- 1、 如何培养“勾勒蓝图”的能力?架构模式与对数据流的敏感度
- 1.1、 储备标准组件库 (The Toolbox)
- 2.2、 掌握经典架构模式 (Architecture Patterns):快速匹配场景
- 2.3、 数据流驱动思维 (Data Flow Driven):数据流决定了架构设计
- 2、 核心组件图
- 第三步:深入设计与难点攻克 (Deep Dive)
- 1、 难点一:如何防止超卖?
- 2、 难点二:如何扛住 100万 QPS?
- 第四步:总结与扩展 (Wrap up)
- 4.1 系统瓶颈
- 4.2 扩展思考
- 总结:系统设计万能公式
核心思想:系统设计不是“背诵架构图”,而是从模糊需求到具体方案的推导过程。
本文以秒杀系统为例,带你走一遍系统设计的标准 4 步流程。
零、 系统设计四步法概览 (Overview)
系统设计通常遵循“四步法”:确定问题范围 → 总体设计 → 细节设计 → 总结。这套框架构成了从“搞清楚要做什么”到“把怎么做写出来”的完整闭环,既保证思考的系统性,也为后续实现、运维和进一步优化奠定基础。
1. 确定问题范围 (Understand & Scope) —— 3-10 分钟
先与面试官或业务方对话,澄清需求、约束与预期规模,把模糊命题转化为可度量目标。这样可以避免后续做无用功,也能让所有后续设计决策有明确坐标系。
关键动作:封底估算 (Back-of-the-envelope Estimation)。用少量假设快速判断流量、存储等核心指标是否可行;如果连最粗略的数字都通不过,后面细节再精巧也无意义。
2. 总体设计 (High-Level Design) —— 10-15 分钟
基于已确认的范围,勾勒高层级蓝图:明确客户端、API 网关、负载均衡、缓存、数据库、消息队列等关键组件如何连接,以及数据与控制流的方向。
这一步相当于在“纸上”做一次快速原型,把系统骨架搭起来。获得反馈后才能知道是否需要调整架构思路。
3. 细节设计 (Deep Dive) —— 10-25 分钟
对关键组件深入(如写入路径的一致性模型、读取路径的缓存策略、分区处理等)。
- 针对性能、可靠性、成本做更精确的估算和方案比选;
- 同时也回答面试官可能提出的“如果流量再大怎么办、机器挂了怎么办”之类追问。
此阶段会把接口、数据结构、错误处理、监控告警等细节补齐,使设计真正可落地。
4. 总结 (Wrap up) —— 3-5 分钟
回顾整个设计:核心决策、主要权衡(如 CAP 选择、一致性 vs. 可用性)、尚未解决的瓶颈。明确下一步验证或迭代方向,为面试收尾或进入编码阶段做准备。
四步的原因
- 逐步收敛:从“问题模糊”到“代码可写”,分阶段不断缩小未知区域,降低一次性决策错误的风险。
- 管理复杂度:把一个开放且庞大的题目拆成小块,每块可独立思考、并行讨论,避免大脑过载。
- 便于沟通:每完成一步都与面试官对齐,可及时纠正方向,展示结构化思考与协作能力。
- 兼顾时间效率:早期估算和高层级框图帮助快速判断可行性,避免在细节上浪费过多时间。
第一步、理解问题与确定边界 (Understand & Scope)
不要急着画图!先搞清楚“我们要解决什么问题”以及“系统的规模有多大”。
1、提问与澄清:挖掘出功能与非功能需求
面试官或业务方给出的需求通常很模糊(例如:“设计一个秒杀系统”)。你需要通过提问来圈定范围。
如果你对系统不熟悉,大脑一片空白,不要慌。最简单的方法是代入用户视角,走一遍核心流程 (User Journey)。
你:“我理解这个系统的核心场景是用户打开 App,浏览商品列表,然后点击购买。请问我们需要覆盖全流程吗?还是只关注下单这一步?” 这样你就把“功能范围”的球踢回给了面试官,同时展示了你对业务流程的理解。
此外,可以从 CRUD 角度通过资源来联想: “这个系统的核心资源是‘商品’。那除了‘读商品’和‘买商品’,我们需要考虑商家的‘发布商品’吗?”
示例对话:
问功能 (Functional):
- 你:我们需要做秒杀商品的管理后台吗?还是只关注用户抢购的前端流程?
- 业务:只关注用户抢购流程(浏览 -> 下单 -> 扣库存)。
- 你:需要支持购物车吗?
- 业务:不需要,秒杀通常是立即购买。
问非功能 (Non-Functional):
- 你:系统需要多高的可用性?允许数据丢失吗?
- 业务:必须高可用,绝对不允许超卖(数据一致性是核心)。
2、 估算规模:三个核心估算维度:流量、存储、带宽
规模决定技术选型。不要试图计算每一个字节,而是要关注数量级通常我们需要关注以下 3 个核心维度:
| 维度 | 详细说明 |
|---|---|
| 流量 (Traffic) | •DAU/MAU:日活/月活用户数。 •QPS (Queries Per Second):平均 QPS 和 峰值 QPS (通常是平均的 2-5 倍)。 •读写比 (Read/Write Ratio):决定是读多写少(用缓存(加速访问))还是写多读少(用 MQ(用队列控制写入速度))。 |
| 存储 (Storage) | •总量:需要存多少数据?(1TB vs 1PB) •增长量:每天新增多少?保留多久? |
| 带宽 (Bandwidth) | • 如果有图片/视频上传下载,需要评估网络带宽瓶颈。 •计算公式:带宽 = Q P S × 平均包大小 带宽 = QPS \times 平均包大小带宽=QPS×平均包大小 •示例 (Instagram):假设每秒上传 50 张照片,每张 2MB。 50 × 2 M B = 100 M B / s 50 \times 2MB = 100MB/s50×2MB=100MB/s。注意:这是字节(Byte),换算成网络带宽(bit)通常要乘以 8,即 800Mbps。 •结论:这通常需要 CDN 或对象存储分流,不能直接打到 Web 服务器。 |
💡 通俗理解 CDN (快递驿站模型)
- 没有 CDN:所有用户都要去唯一的**总仓库(Web 服务器)**取货。路途远(延迟高),且仓库门口容易堵死(带宽耗尽)。
- 有了 CDN:总仓库把货物(图片/视频)提前发到各地的驿站(边缘节点)。用户直接去家门口取货,速度快且不占总仓库的门路。
示例:秒杀系统估算
- 用户量:假设 1000 万人参与。
- QPS (每秒查询率):
- 假设活动开始瞬间,10% 的用户刷新页面:1000 万 × 10 % = 100 万 1000万 \times 10\% = 100万1000万×10%=100万QPS。
- 结论:单机数据库(MySQL)只能抗几千 QPS,必须用缓存(Redis)。
- 库存量:只有 100 个 iPhone。
- 结论:写请求(下单)只有 100 个是有效的,其余 99.99% 都是无效流量,必须在上游拦截。
第二步:高层架构设计 (High-Level Design)
画出系统的宏观蓝图,确定核心组件及其交互关系。
1、 如何培养“勾勒蓝图”的能力?架构模式与对数据流的敏感度
很多同学会问:“我知道要画图,但我怎么知道该画哪些组件?怎么知道该把 Redis 放在哪里?”
这其实考察的是架构模式的储备和数据流的敏感度。要具备这种能力,你需要建立以下思维模型:
1.1、 储备标准组件库 (The Toolbox)
建立心中的“乐高积木盒”。遇到需求时(如“存数据”、“抗高并发”),能迅速条件反射出对应的组件,而不是大脑一片空白。熟悉这些标准件的适用场景和优缺点,是画出架构图的前提。
一、计算 (Compute)
也就是我们写的后端代码运行的地方。
- Web Server (无状态):处理 HTTP 请求,不存数据,随时可以扩缩容 (Horizontal Scaling)。
- Worker (异步任务 / 离线任务):也就是常说的Background Jobs。
- 通俗解释:Web Server 像是前台服务员,负责接待客户,必须秒回(同步);Worker 像是后厨或工厂,负责处理耗时的脏活累活(异步)。
- 场景:用户点击“导出报表”。Web Server 只是记下来“好,收到了”,然后返回“处理中”。Worker 慢慢从 MQ 里拿出任务,花 5 分钟生成 Excel,最后发邮件给用户。
- FaaS (Serverless):如 AWS Lambda。适合短时、突发任务(如图片上传后自动压缩)。
二、存储 (Storage)
数据到底放哪?
- 关系型数据库 (MySQL/PostgreSQL):可信赖的账本。存用户资料、订单、资金流水。必须保证 ACID 事务,哪怕慢一点。
- 缓存 (Redis/Memcached):临时记事本。存热点数据(如热门商品详情),读写极快(内存),但断电可能丢,且容量有限。
- NoSQL/文档型 (MongoDB/DynamoDB):灵活的档案袋。存商品属性、评论、日志。Schema 灵活(不用预先建表),写入吞吐高。
- 搜索引擎 (Elasticsearch):全文检索目录。专门做复杂的搜索和筛选(如电商搜商品、日志搜错误)。
- 对象存储 (S3/OSS):无限大硬盘。存图片、视频、备份文件。便宜且容量无限,但不能像数据库那样修改内容(通常是覆盖上传)。
三、网络/分发
- CDN (静态资源加速):通常搭配对象存储使用。
- 实现:阿里云 CDN / AWS CloudFront + OSS / S3。用户上传图片到 OSS,自动同步到 CDN 边缘节点。
- DNS (域名解析):
- 实现:Route53 / Cloudflare。支持“地理位置路由 (GeoDNS)”,让北京用户解析到北京机房 IP。
四、通信 (Communication)
如何让组件说话?取决于你需要多快、多紧密。
| 通信类型 | 具体实现 | 说明 & 场景 |
|---|---|---|
| 同步调用 (Request/Response) | HTTP (REST/GraphQL) | 最通用,兼容性好。 场景:外部 API、前后端交互、简单服务间调用。 |
| RPC (gRPC/Thrift) | 高性能,强类型约束,节省流量(二进制)。 场景:微服务内部的高频调用。 | |
| 异步解耦 (Async/Decoupling) | Message Queue (Kafka/RabbitMQ) | 场景:削峰填谷(秒杀流量缓冲)、解耦(下单成功后触发积分/发货/通知)、最终一致性事务。 |
| 实时推送 (Real-time) | WebSocket | 双向持久连接。 场景:聊天室、即时游戏、协同编辑。 注意:WebSocket是协议(类似 HTTP),而Netty是 Java 中用来实现这个协议的高性能框架(工具)。 |
| SSE (Server-Sent Events) | 服务端单向推送。 场景:股票行情、站内信通知、大模型流式输出。 |
五、控制 (Control)
系统的神经中枢。
- Gateway (网关):系统的唯一入口(保安/前台)。
- 功能:身份认证 (Auth)、限流 (Rate Limiting)、路由转发、协议转换 (HTTP -> RPC)。
- 选型:Nginx(高性能反向代理),Spring Cloud Gateway(Java生态),Kong/APISIX(云原生).
- Service Discovery & Config (注册中心/配置):系统的通讯录和公告栏。
- 功能:服务 A 找服务 B 的 IP (服务发现);不重启修改降级开关 (动态配置)。
- 选型:Zookeeper(老牌强一致),Etcd(K8s标配),Nacos(阿里系, 国内流行).
2.2、 掌握经典架构模式 (Architecture Patterns):快速匹配场景
架构设计不是“发明创造”,而是“模式匹配”。90% 的业务场景(如电商、社交、直播)在业界都已经有成熟的、经过验证的解决方案。
直接套用这些模式,能极大地降低设计出错的风险,并节省时间去处理真正的业务细节。
你需要先识别出你的业务属于哪类场景,然后调出对应的“模板”进行微调:
| 场景 | 典型例子 | 设计套路 & 核心架构链路 |
|---|---|---|
| 读多写少 | 微博热搜 电商首页 知乎文章 | Cache-Aside + 多级缓存,核心:保护数据库 架构:Client -> CDN -> Nginx -> App Server ->Redis-> DB |
| 写多读少 | 日志收集 埋点上报 物联网传感器 | 削峰填谷 + 批量写入,核心:别把数据库写死 架构:Client ->Kafka-> Worker (Batch) -> DB / Elasticsearch |
| 实时通信 | 微信聊天 王者荣耀 在线文档协作 | 长连接 + 推送服务,核心:保持通道不断 架构:Client ->WebSocket Gateway-> Connection Manager -> Logic Server |
2.3、 数据流驱动思维 (Data Flow Driven):数据流决定了架构设计
做架构设计时,不要先凭空去想要用哪些组件、怎么画架构图,而是要跟着数据和请求的流转过程去思考。
你可以这样梳理一个完整请求:
- 入口:用户请求先经过 DNS、CDN、Nginx
- 读写:读操作走缓存,写操作走 DB 或 MQ
- 计算:业务逻辑交给后端服务处理
- 下游通知:需要异步处理时,通过 MQ 转发给下游服务
把这个请求从进入系统到最终处理完成的整条路径梳理清楚,架构图自然就出来了。
2、 核心组件图
对于秒杀系统,我们可以画出以下数据流:
为什么这么设计?
- 网关 (Gateway):用于拦截流量(限流、黑名单),保护后端。
- Redis 缓存:因为估算出有 100万 QPS,MySQL 扛不住,所以读库存和扣库存必须在 Redis 进行。
- 消息队列 (MQ):因为写请求(下单)可能有峰值,数据库写入慢,需要 MQ 来“削峰填谷”,慢慢写入数据库。
- CDN 静态资源:秒杀页面包含大量图片,带宽估算巨大,必须让用户从就近节点加载,减少源站压力。
- 服务隔离 (Isolation):秒杀流量太凶猛,为了不把主站(如购物车、订单查询)搞挂,通常将秒杀系统作为独立服务部署。
第三步:深入设计与难点攻克 (Deep Dive)
挑选最棘手的问题,展示你的技术深度。对于秒杀系统,最棘手的是超卖和高并发。
1、 难点一:如何防止超卖?
业界通用的最佳实践是使用Redis + Lua 脚本来实现高并发下的原子扣减。
为什么不用数据库?
- 传统数据库(如 MySQL)的“查库存 -> 改库存”在并发下会产生竞态条件导致超卖;而使用乐观锁(
UPDATE ... WHERE num > 0)虽然能防超卖,但高并发下会产生严重的锁竞争,拖慢系统响应。
- 传统数据库(如 MySQL)的“查库存 -> 改库存”在并发下会产生竞态条件导致超卖;而使用乐观锁(
Redis Lua 方案详解:
- 原子性保证:Redis 会将 Lua 脚本作为一个整体执行,中间绝不会插入其他命令。这相当于在内存中通过“单线程串行化”解决了并发竞争问题,天然防止超卖。
- 执行逻辑:
- 检查库存:
GET stock_id - 判断库存 > 0?
- 如果 > 0:执行
DECR stock_id并返回成功。 - 如果 <= 0:直接返回失败。
- 注意:上述逻辑在 Lua 脚本中是一次性发给 Redis 执行的。
- 检查库存:
- 异步落地:Redis 扣减成功后,立即返回前端“抢购成功”,同时通过MQ (消息队列)异步将订单写入数据库,实现最终一致性。
2、 难点二:如何扛住 100万 QPS?
- 动静分离:
- 秒杀页面(HTML/CSS/JS)是静态的,推送到 CDN(内容分发网络),用户就近访问,流量不打到服务器。
- 只有“获取库存”、“下单”这两个接口打到服务器。
- 漏斗模型 (Funneling):
- 第1层 CDN:拦截 90% 的静态资源请求。
- 第2层 网关:限流(如每秒只放行 10万 请求),拦截恶意刷单 IP。
- 第3层 Redis:库存扣完后,直接返回“已抢光”,拦截剩余请求。
- 第4层 DB:只有那 100 个抢到库存的请求才会真正写数据库。
第四步:总结与扩展 (Wrap up)
回顾设计,指出不足,讨论未来优化方向。
4.1 系统瓶颈
- Redis 挂了怎么办?-> 需要设计 Redis 主从高可用,甚至多级缓存(本地缓存 + Redis)。
- MQ 消息丢了怎么办?-> 导致库存扣了但没生成订单(少卖)。需要消息持久化和补偿机制(对账)。
4.2 扩展思考
- 如果不是秒杀,而是普通的电商下单,架构会有什么不同?(不需要极致的限流和 Lua 脚本,更注重事务完整性)。
总结:系统设计万能公式
- 问:搞清需求,不要瞎猜。
- 算:算算 QPS,决定要不要分布式/缓存。
- 画:画出大图,确立组件。
- 钻:钻研难点(高并发/一致性/安全)。