Skip to content

07 · 从 0 到 1 设计一个系统(实战方法论)

前面六章给了你一套思维和一箱工具;这一章把它们拧成一条照着做就能产出架构方案的流水线——并带你用一个真实例子从头走一遍。


开场:别一上来就画框

新手接到「帮我设计个 X」,最常见的反应是:抓起笔,在白板中央画一个框,写上「服务器」,再画个数据库,然后……就卡住了。

资深的人不一样。他们接到需求后的前几分钟,一个框都不画,只问问题:谁用?用来干嘛?多少人用?要多快?能不能丢数据?钱从哪来?——因为他们知道一件事:

架构不是「画」出来的,是从约束里「逼」出来的。 你没搞清楚约束,画什么都是瞎画。

这一章给你一套 8 步流程。它不是什么神秘公式,而是把前六章学的东西排了个先后顺序:先有需求与约束(02),再估规模,再定边界,再设计数据(05),再画图(03),再深入组件(04),再找瓶颈(06),最后回头审视取舍。

这套流程同时适用于两个场景:真刀真枪设计一个新系统,以及系统设计面试。两者的底层动作其实一模一样——区别只是面试压缩在 45 分钟里、且面试官在乎的是你思考的过程而非最终那张图。


一、八步流程总览

先看全景,再逐个拆解。注意:这八步不是一条直线走完就完事,而是一个会反复回头的循环。

                  ┌─────────────────────────────────────────────┐
                  │                                             │
                  ▼                                             │
   ① 澄清需求与范围 ──▶ ② 估算规模 ──▶ ③ 定义用例/API 边界       │
                                              │                 │
                                              ▼                 │
   ⑤ 画高层架构 ◀────────────────── ④ 设计数据模型             │
        │                                                       │
        ▼                                                       │
   ⑥ 逐个深入关键组件 ──▶ ⑦ 找瓶颈,针对性扩展                  │
                                              │                 │
                                              ▼                 │
                              ⑧ 回顾取舍 / 列风险 / 列未决问题 ──┘
                                  (发现问题就回到任意一步重来)
步骤你在干什么对应前面哪一章
① 澄清需求与范围先问问题,把模糊变明确,圈定 MVP 边界02
② 估算规模信封背面算一笔:用户量、QPS、数据量、读写比本章新增
③ 定义核心用例 / API 边界系统对外暴露哪些动作?入口长什么样?02
④ 设计数据模型核心实体、关系、各自放进哪种存储05
⑤ 画高层架构先 Context 再 Container,先粗后细03
⑥ 逐个深入关键组件挑灵魂部件往里钻,套合适的模式04
⑦ 找瓶颈,针对性扩展涨 100 倍,第一个死哪?怎么破?06
⑧ 回顾取舍与风险我放弃了什么?哪里还没想清楚?06 + 08

下面逐步讲方法,讲完立刻用一个完整例子把这八步走一遍。


步骤 ①:澄清需求与范围(先问问题,别急着画图)

这是最被低估、却最能拉开差距的一步。一个模糊的需求(「做个像 Twitter 的东西」)背后藏着十几个没说出口的假设,你的任务是把它们逼出来

要问的问题分三类:

  • 功能边界:这个 MVP 到底要做什么、不做什么?(「要不要支持私信?」「要不要搜索?」——把不做的明确划出去,比列要做的更重要)
  • 用户与规模:谁用?有多少人?增长多快?(这步的答案直接喂给步骤 ②)
  • 质量与约束:要多快?能不能短暂不可用?数据能不能丢?有没有合规/成本红线?(这正是 06 质量属性 里那张清单)

关键心法:你的目标不是「满足所有需求」,而是「确认哪些需求其实不重要」。 范围每砍掉一块,架构就简单一个量级。新手什么都想做,高手拼命做减法。

面试里,这一步还有个隐藏作用:面试官故意把题目出得很模糊,就是想看你会不会问。 上来就闷头画图的人,基本已经挂了一半。


步骤 ②:估算规模(信封背面估算)

back-of-the-envelope estimation——字面意思「在信封背面随手一算」。不追求精确,只求数量级对不对

为什么必须算?因为**「1 万用户」和「1 亿用户」是两个完全不同的系统**。前者一台机器一个数据库就够,后者每一层都要考虑分片、缓存、多活。不估规模就设计,等于不知道要盖平房还是摩天楼就开始浇地基。

估算只需要几个常识级的「锚点数字」记在脑子里:

  ┌──────────────────────────────────────────────────┐
  │  几个值得背下来的「锚点」                            │
  │                                                    │
  │  • 一天 ≈ 86,400 秒,约等于 10^5 秒(估算时就用它)  │
  │  • 1 千万日活,如果每人每天来 10 次                  │
  │       → 1 亿次/天 ÷ 10^5 秒 ≈ 1,000 QPS(平均)     │
  │  • 峰值通常是平均值的 2~5 倍 → 按 ~3,000 QPS 备容    │
  │  • 读写比:社交/内容类常是「读多写少」,10:1 起步   │
  │  • 一条文本记录 ~ 数百字节;一张图 ~ 数百 KB ~ 几 MB │
  └──────────────────────────────────────────────────┘

一个完整算例(先感受手感,正式例子在后面):

假设做一个发帖应用,1000 万日活,人均每天发 1 条、刷 50 条

  • 写 QPS:1000 万条/天 ÷ 10^5 秒 = 100 写/秒(平均),峰值 ×3 ≈ 300。
  • 读 QPS:1000 万 × 50 = 5 亿次/天 ÷ 10^5 = 5000 读/秒,峰值 ≈ 15000。
  • 读写比:5000 : 100 = 50 : 1——典型的读多写少,这一条立刻告诉你:缓存和读扩展是重点,写不是瓶颈。
  • 存储量:一条帖子算 1 KB,1000 万/天 × 365 ≈ 3.6 TB/年(纯文本)。如果带图,每帖均摊 500 KB,就是 1.8 PB/年——量级差了 500 倍,存储方案完全不同

看到没?几个除法就把架构的重心给定了:这是个读密集系统,要在读路径上堆缓存;文本可以塞数据库,图必须走对象存储 + CDN。这些结论,全是算出来的,不是拍脑袋的。

💡 估算的价值不在数字精确,而在它逼你想清楚「这个系统到底是被什么压垮的」。 读爆?写爆?存储爆?带宽爆?——答案不同,后面七步全跟着变。


步骤 ③:定义核心用例 / API 边界

把系统当成一个黑盒,先想清楚:外界能对它做哪些动作? 这就是 API 边界——不写具体协议,只列「动作清单」。

比如一个发帖应用,核心动作可能就五六个:发帖删帖看某人主页刷信息流关注/取关先列出来,然后挑出 1~2 个「主航道」——通常就是用户最高频、最能体现系统特点的那个动作(发帖应用就是「刷信息流」)。

为什么先定边界? 因为边界一旦清楚,内部怎么实现就有了靶子。你不会再纠结「要不要加这个模块」,而是问「这个模块服务于哪个用例」——服务不了任何用例的模块,就是过度设计(_TEMPLATE.md 里反复强调:没有「为什么」的部件就是过度设计)。

面试中,这一步还帮你收敛战场:主动说「这个系统能做的事很多,但今天我重点设计『刷信息流』这条路,其它先放一放」——这是成熟度的体现。


步骤 ④:设计数据模型(承接 05)

05 数据与状态 的核心观点是:系统真正的难点不是逻辑,是数据。所以一旦用例清楚了,立刻设计数据,而不是先设计服务

这一步做两件事:

  1. 画概念模型:核心实体是什么?它们之间什么关系?(用户 — 帖子 — 关注关系 — 点赞……用最朴素的方框和连线画出来,不写建表语句)
  2. 给每类数据选存储形态:回忆 05 的关键句——不同数据的「访问形态」不同,就该放进不同类型的存储

照搬 AI 对话产品模板第 7 节 那张表的思路,你应该能给每类数据填出这样一行:

数据访问形态适合的存储类型
用户 / 关注关系要事务、要按关系查关系型
帖子内容写多、按 ID 取、结构简单文档型 / 追加日志
信息流(每人看到的列表)海量、要快、可重算内存级缓存
图片 / 视频大、不可变、按 URL 取对象存储 + CDN

新手通病:所有东西一股脑塞进一个关系型数据库,然后用 LIKE 做搜索、用大表存历史。 数据建模这一步做对了,后面架构基本就顺了;做错了,后面再多缓存也救不回来。


步骤 ⑤:画高层架构(先 Context 再 Container,承接 03)

现在,也只有现在,才到了「画框」的时候。但要按 03 C4 模型 的顺序来:

  • 先画 Context(系统上下文):把你的系统当成一个黑盒,只画它和外部世界(用户、第三方支付、邮件服务……)的关系。这一步确认「系统的边界在哪、和谁打交道」。
  • 再画 Container(容器):把黑盒切开一层,画出几大块:客户端、API 网关、几个核心服务、几种存储。这一层就够画第一版架构图了,别急着往下钻到代码级。

铁律:先粗后细。 第一张图就该是「五六个框 + 箭头」的粗线条,丑一点没关系,能讲清楚数据怎么流就行。把每个框内部画清楚,是后面步骤 ⑥ 的事。一上来就画二十个框的人,往往是在用「画图的勤奋」掩盖「没想清楚的偷懒」。


步骤 ⑥:逐个深入关键组件

第一张粗图画完,挑出 **1~2 个「灵魂部件」**往里钻——不是每个框都要深挖,只挖那个决定系统成败的。(还记得 AI 对话产品 那句吗:灵魂部件是推理服务,其余一切都在「保护和喂养这块 GPU」。每个系统都有它的那块「GPU」。)

深入时,就是 04 十大核心架构模式 派上用场的地方:这个组件要解耦?也许用事件驱动;读写形态差太大?也许上 CQRS;要扛突发流量?中间加消息队列削峰。模式不是用来炫技的,是用来回答「这个组件遇到的具体问题」的。

怎么判断哪个是灵魂部件? 回到步骤 ②:被规模压垮的那个,就是它。读爆了,灵魂部件就是「信息流的读路径」;写爆了,就是「写入与扇出」;算力爆了(像 AI 产品),就是「推理服务」。


步骤 ⑦:找瓶颈,针对性扩展(承接 06)

问自己那个经典问题:「用户从现在涨 100 倍,第一个撑不住的地方在哪?」 然后是第二个、第三个。

这一步直接复用步骤 ② 的估算结果。既然算出来是 50:1 的读多写少,那第一个瓶颈八成在读路径:数据库读到冒烟 → 破解手段是缓存、读副本、把信息流预计算好。每个瓶颈都对应着一类成熟的破解套路(这正是每个模板**第 9 节「规模化与瓶颈」**在讲的东西)。

关键:不要泛泛地说「加缓存、上分片」。 要针对你这个系统算出来的具体瓶颈说。瓶颈在读就别去优化写,瓶颈在带宽就别去抠 CPU——对着不疼的地方猛敲,是新手最常见的浪费。


步骤 ⑧:回顾取舍、列出风险与未决问题

最后一步,也是最显功力的一步:回过头审视自己刚做的所有决定,把代价摆到台面上。

  • 取舍:我选了 A,放弃了 B,代价是什么?(「我预计算信息流换来了读的飞快,代价是写的时候要扇出、且数据短暂不一致」)
  • 风险:哪个假设如果错了,整个设计就崩?(「我假设读写比是 50:1,要是来个明星用户被千万人关注,扇出就爆了」)
  • 未决问题:哪些我现在还没想清楚,需要标记出来留待以后?

这一步为什么重要? 因为没有完美架构,只有「我清楚自己在拿什么换什么」的架构。能主动说出自己方案的弱点,恰恰证明你真的想清楚了。而这些「取舍与理由」,正是下一章 08 ADR 要你郑重记下来的东西——今天你说出口的每一句「我选 A 放弃 B 是因为……」,都该变成一条架构决策记录。

面试里,主动暴露弱点是加分项而非减分项;工作中,这一步则是你三个月后回来 review 时,救你命的那份「当时我是怎么想的」。


二、完整实战:从 0 设计一个「短链接服务」

光说方法太干,我们挑一个麻雀虽小、五脏俱全的系统,把八步从头走一遍。

为什么选短链接(把长 URL 变成 s.xx/aB3xY 这种短链)? 因为它需求极简、人人都懂,却逼着你触碰几乎每一个核心架构议题:海量读、唯一 ID、缓存、读写比悬殊、存储选型、热点……是「小系统讲透大道理」的最佳标本。

重要提醒:下面展示的是「一个人的思考过程」,不是「标准答案」。 你完全可以在某一步做不同的选择——只要你说得清为什么。


① 澄清需求与范围

接到「做个短链接服务」,我先问、并自己拍板一组假设:

  • 要做:把长链接缩短成短链;访问短链时跳转到原链接;短链可设过期时间。
  • 不做(MVP 砍掉):自定义短链别名、点击统计分析、用户账号体系。先把核心跑通,这些都是后话。
  • 质量约束:
    • 跳转必须极快(用户点了要立刻跳,延迟 > 几百毫秒就难受)。
    • 跳转服务几乎不能挂(挂了 = 全网这家的短链全失效,灾难级)。
    • 短链一旦生成,对应关系不能变、不能丢
    • 短链要(越短越好,这是产品价值本身)。

注意我做的最重要的动作是划掉了三个功能。范围一收窄,系统瞬间清爽。


② 估算规模(信封背面)

假设:每天新建 1000 万条短链,读写比 100:1(短链就是为了被疯狂点击的,典型读极多写极少)。

  写 QPS  = 1000 万 / 天 ÷ 10^5 秒 ≈ 100 次/秒   (峰值 ×3 ≈ 300)
  读 QPS  = 100 × 写 = ~10,000 次/秒              (峰值 ≈ 30,000)

  存储量(5 年):
    1000 万/天 × 365 × 5 ≈ 180 亿条
    每条 ~ 500 字节(短码 + 原链 + 元数据)
    → 180 亿 × 500 B ≈ 9 TB

算出三个决定性结论:

  1. 读 QPS 是写的 100 倍 → 这是个极端读密集系统,架构重心 100% 在读路径,缓存是命根子
  2. 5 年才 9 TB → 数据量不算夸张,单一存储 + 缓存能扛,暂时不用一上来就分库分表。
  3. 要支撑 180 亿条 → 短码至少要够长以避免撞码。用 0-9a-zA-Z 共 62 个字符,62^7 ≈ 3.5 万亿,7 位短码绰绰有余。这个结论直接定义了核心算法。

看,「短码该用几位」这种核心设计,是从估算里算出来的,不是猜的。 这就是步骤 ② 的威力。


③ 定义核心用例 / API 边界

黑盒对外就两个动作:

  • 创建:给一个长链接,返回一个短码。(低频,~100 QPS)
  • 跳转:给一个短码,返回对应的长链接(并跳转)。(超高频,~10000 QPS)

主航道一目了然:跳转 它的流量是 创建 的 100 倍,整个系统的成败就看它快不快、稳不稳。接下来所有设计,都优先伺候这条主航道。


④ 设计数据模型

概念模型简单到极致——核心就一个实体:

   ┌────────────────────────────────┐
   │  短链映射 (ShortLink)           │
   │  ──────────────────             │
   │  短码      (主键, 7 位)         │
   │  原始长链接                     │
   │  创建时间                       │
   │  过期时间                       │
   └────────────────────────────────┘

选存储形态(回忆 05:看访问形态):

数据访问形态适合的存储
短码 → 长链 的映射按短码精确查,海量,几乎不改KV 存储(天生为「按 key 取 value」而生)
同一份映射的热点部分极高频读、可容忍偶尔回源内存级缓存

关键判断:这是个纯 key-value 查找问题(拿短码换长链),根本不需要关系型数据库的关联查询和复杂事务。硬上关系型,是把简单问题复杂化。选 KV 存储,是「让数据形态决定存储类型」的教科书示范。


⑤ 画高层架构(先 Context,再 Container)

Context(把系统当黑盒):

   ┌──────────┐    点短链 / 提交长链    ┌─────────────────┐
   │  用户/浏览器 │ ───────────────────▶ │  短链接服务(黑盒)│
   └──────────┘ ◀─────────────────── └─────────────────┘
                    302 跳转 / 返回短码

Container(切开一层,这就是第一版架构图——粗线条、够用就好):

         用户

          │  ① 创建(少)        ② 跳转(极多)

   ┌─────────────────────────────────────────────┐
   │            接入层 / API 网关                  │
   │         (限流、路由、防刷)                    │
   └───────┬──────────────────────────┬──────────┘
           │ 创建                      │ 跳转
           ▼                          ▼
   ┌───────────────┐          ┌───────────────────┐
   │  创建服务      │          │   跳转服务         │
   │  生成唯一短码  │          │  查短码→拿长链→302 │
   └───────┬───────┘          └────────┬──────────┘
           │ 写                        │ 先读缓存
           │                           ▼
           │                  ┌──────────────────┐   命中?直接返回
           │                  │   内存级缓存      │────────────────▶
           │                  └────────┬─────────┘
           │ 写                        │ 未命中,回源
           ▼                           ▼
   ┌─────────────────────────────────────────────┐
   │              KV 存储(短码 → 长链)            │
   └─────────────────────────────────────────────┘

注意我刻意把「创建」和「跳转」拆成两条路、两个服务——因为步骤 ② 告诉我它俩流量差 100 倍、读写形态完全不同。让它们各自独立扩展,跳转服务可以疯狂加机器,创建服务一两台就够。这其实就是 04读写分离 / CQRS 思想的朴素体现。


⑥ 逐个深入关键组件

灵魂部件有两个,各钻一下:

(a) 跳转服务(主航道,关乎「快」和「稳」)

读路径必须极致地快,所以套多级缓存:请求先打内存缓存,命中就直接返回(覆盖绝大多数热点短链);未命中才回源 KV 存储,取到后回填缓存。因为「短码→长链」的映射几乎永不变化,缓存可以放得又久又狠,命中率天然极高——这正是步骤 ② 那个 100:1 读写比送给我们的红利。

(b) 创建服务(关乎「短码怎么来、会不会撞」)

核心难题:怎么生成一个全局唯一、又短、又不易被猜的 7 位码? 这是个经典岔路口,留到下一节专门掰扯。

为什么只深挖这两个? 因为 API 网关、KV 存储都是「成熟构件,按部就班配置即可」,而短码生成读缓存才是这个系统独有的、做错了会致命的地方。把精力压在刀刃上。


⑦ 找瓶颈,针对性扩展

「跳转量再涨 100 倍(到百万 QPS)会死哪?」逐个排查:

  • 第一个瓶颈:跳转读流量打爆后端。 → 破解:缓存命中率本就高,再往用户侧推——把热点短链直接缓存到 CDN / 边缘节点,让大多数跳转根本到不了源站。这是读密集系统的标准答案。
  • 第二个瓶颈:数据涨到几百 TB,单 KV 撑不住。 → 破解:按短码分片。短码是随机的,天然均匀,分片极其简单——这又是步骤 ④ 选对了 KV、步骤 ② 让短码够随机的连锁红利。
  • 第三个瓶颈:创建服务的「发号」成为单点。 → 破解:不要每次都去问一个中心要号(它会成瓶颈),改成给每台创建机预先批发一段号段,本地慢慢发,发完再批发下一段。

看出规律没?几乎每个瓶颈的破解,都在「兑现」前面某一步埋下的伏笔。 前面想得越清楚,这里越省力——这就是为什么不能跳步。


⑧ 回顾取舍、列风险与未决问题

把这一版方案的代价和软肋摆出来(这一步不是走过场,是整个设计最诚实的部分):

关键取舍:

我选了放弃了因为
KV 存储关系型的关联查询能力这就是个 key-value 查找,用不上关联
重度缓存 + CDN一定的实时性(改了链接,缓存可能还是旧的)映射几乎不变,这个代价几乎为零
创建/跳转拆两条路一点架构简洁性(两个服务而非一个)读写比 100:1,值得为各自独立扩展付这点复杂度
预批发号段发号短码的严格连续/可预测换来无单点、可水平扩展;短码本就不该可预测(安全)

风险与未决问题(诚实标注):

  • ⚠️ 热点短链(某条链接突然被亿级点击):缓存能扛大部分,但缓存失效的瞬间可能压垮源站(缓存击穿)。——未决:要不要给热点 key 加单独的保护?
  • ⚠️ 短码耗尽/撞码:7 位算过够用 5 年,但增长若远超预期,需提前预案升到 8 位。——未决:监控短码空间使用率,设报警阈值。
  • ⚠️ 删除/过期的回收:过期短码要不要回收复用?复用会引入「旧链接被指向新目标」的安全风险。——未决:倾向不复用,但要确认存储成本能否承受。

这三条「未决问题」不是设计的失败,恰恰是设计成熟的标志。 一个号称「没有任何未决问题」的方案,只说明作者还没想到那么深。把它们记下来——下一章你会知道,这些正是该写进 ADR 的内容。


三、贯穿全程的一条主线:架构是「迭代」出来的

如果这一章你只能记住一句话,那就是这句:

没有人能一次把架构画对。好架构是「先画个粗的,再在压力下一轮轮长出来的」。

回头看那个流程图为什么是个带回环的循环——因为真实过程从来不是 ①→②→…→⑧ 一条直线走到底:

  • 你在步骤 ⑦ 发现读会爆,回到 ④ 改数据模型(加一层缓存表)。
  • 你在步骤 ⑧ 发现某个假设站不住,回到 ① 重新和需求方确认范围。
  • 上线三个月后来了真实流量,你整个重走一遍,这次的「估算」换成了「真实监控数据」。
   第一版(粗)          第二版(细)           第 N 版(实战打磨)
   ┌────────┐  发现瓶颈  ┌────────┐  真实流量   ┌────────┐
   │ 五六个框 │ ───────▶ │ 加缓存  │ ─────────▶ │ 分片/CDN │ ─▶ …
   │ 能讲通即可│         │ 拆服务  │            │ 多活/降级│
   └────────┘          └────────┘            └────────┘
        ▲                                          │
        └──────────── 永远在长大 ──────────────────┘

这也正是整个仓库三条阅读原则里那句「架构是会长大的」(见 根 README)。别拿成熟期的图去套 MVP——你的短链服务第一版可能就是「一个服务 + 一个 KV + 一层缓存」,等真红了再长出 CDN、分片、号段。过度设计想不到一样有害。


📌 真实案例:拿模板验证你的推演

本章用八步从 0 设计了短链接服务。现在去验证:打开本仓库 短链接服务模板,对照它的第 8 节(关键决策)和第 9 节(瓶颈)——看你推演时做的取舍,和它一不一样。不一样不要紧,能说清各自为什么,你就在用架构师的方式思考了。

任何一个 模板 都能这么用:盖住后半,自己先用八步推演,再对照。


本章小结

  • 架构不是画出来的,是从约束里逼出来的。 接到需求先问问题、先估规模,别急着画框。
  • 一套照着做就能产出方案的八步:① 澄清需求范围 → ② 信封背面估规模 → ③ 定用例/API 边界 → ④ 设计数据模型 → ⑤ 先 Context 再 Container 画图 → ⑥ 深挖灵魂部件 → ⑦ 找瓶颈针对性扩展 → ⑧ 回顾取舍与风险。每一步都对应前六章的某个工具。
  • 估算(信封背面)是被低估的超能力:几个除法就能定出系统重心(读爆?写爆?存储爆?),后面所有设计都跟着走。
  • 先粗后细:第一张图就该是丑陋的「五六个框」,够讲清数据流就行。深挖只挖灵魂部件,精力压在刀刃上。
  • 第八步最显功力:能主动说出自己方案放弃了什么、哪里还没想清楚,才证明你真想透了。
  • 架构是迭代出来的:流程是个会反复回头的循环,而不是一条直线。别想一次画对,也别拿成熟期的架构套 MVP。

现在,轮到你了

动手练习:打开 ../templates/,任选一个系统(电商、社交信息流、实时通讯、视频流媒体……),先盖住第 4~9 节,自己拿这八步从头推演一遍——尤其别跳过步骤 ②,亲手算一笔它的 QPS 和存储量。

推演完,再翻开模板对照:你的「灵魂部件」找对了吗?你估出的瓶颈,和它第 9 节说的一致吗?它在第 8 节做的取舍,和你做的一样吗?不一样不要紧——能说清各自为什么,你就已经在用架构师的方式思考了。

推演的时候,你一定会做出一连串「我选 A 不选 B」的决定。这些决定此刻在你脑子里清清楚楚,但三个月后会忘得一干二净——到时候连你自己都会问「当初为什么这么定?」

怎么把这些宝贵的「为什么」留住,让架构能随业务一起健康长大?这就是下一章的主题。


相关链接