ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

【系统设计(1)】系统设计入门方法论

【系统设计(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. 可用性)、尚未解决的瓶颈。明确下一步验证或迭代方向,为面试收尾或进入编码阶段做准备。

四步的原因

  1. 逐步收敛:从“问题模糊”到“代码可写”,分阶段不断缩小未知区域,降低一次性决策错误的风险。
  2. 管理复杂度:把一个开放且庞大的题目拆成小块,每块可独立思考、并行讨论,避免大脑过载。
  3. 便于沟通:每完成一步都与面试官对齐,可及时纠正方向,展示结构化思考与协作能力。
  4. 兼顾时间效率:早期估算和高层级框图帮助快速判断可行性,避免在细节上浪费过多时间。

第一步、理解问题与确定边界 (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):数据流决定了架构设计

做架构设计时,不要先凭空去想要用哪些组件、怎么画架构图,而是要跟着数据和请求的流转过程去思考。

你可以这样梳理一个完整请求:

  1. 入口:用户请求先经过 DNS、CDN、Nginx
  2. 读写:读操作走缓存,写操作走 DB 或 MQ
  3. 计算:业务逻辑交给后端服务处理
  4. 下游通知:需要异步处理时,通过 MQ 转发给下游服务

把这个请求从进入系统到最终处理完成的整条路径梳理清楚,架构图自然就出来了。

2、 核心组件图

对于秒杀系统,我们可以画出以下数据流:

用户

网关/负载均衡

秒杀服务

Redis 缓存

消息队列

数据库

为什么这么设计?

  1. 网关 (Gateway):用于拦截流量(限流、黑名单),保护后端。
  2. Redis 缓存:因为估算出有 100万 QPS,MySQL 扛不住,所以读库存和扣库存必须在 Redis 进行。
  3. 消息队列 (MQ):因为写请求(下单)可能有峰值,数据库写入慢,需要 MQ 来“削峰填谷”,慢慢写入数据库。
  4. CDN 静态资源:秒杀页面包含大量图片,带宽估算巨大,必须让用户从就近节点加载,减少源站压力。
  5. 服务隔离 (Isolation):秒杀流量太凶猛,为了不把主站(如购物车、订单查询)搞挂,通常将秒杀系统作为独立服务部署。

第三步:深入设计与难点攻克 (Deep Dive)

挑选最棘手的问题,展示你的技术深度。对于秒杀系统,最棘手的是超卖和高并发。

1、 难点一:如何防止超卖?

业界通用的最佳实践是使用Redis + Lua 脚本来实现高并发下的原子扣减。

  • 为什么不用数据库?

    • 传统数据库(如 MySQL)的“查库存 -> 改库存”在并发下会产生竞态条件导致超卖;而使用乐观锁(UPDATE ... WHERE num > 0)虽然能防超卖,但高并发下会产生严重的锁竞争,拖慢系统响应。
  • Redis Lua 方案详解:

    1. 原子性保证:Redis 会将 Lua 脚本作为一个整体执行,中间绝不会插入其他命令。这相当于在内存中通过“单线程串行化”解决了并发竞争问题,天然防止超卖。
    2. 执行逻辑:
      • 检查库存:GET stock_id
      • 判断库存 > 0?
      • 如果 > 0:执行DECR stock_id并返回成功。
      • 如果 <= 0:直接返回失败。
      • 注意:上述逻辑在 Lua 脚本中是一次性发给 Redis 执行的。
    3. 异步落地: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 脚本,更注重事务完整性)。

总结:系统设计万能公式

  1. 问:搞清需求,不要瞎猜。
  2. 算:算算 QPS,决定要不要分布式/缓存。
  3. 画:画出大图,确立组件。
  4. 钻:钻研难点(高并发/一致性/安全)。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进