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 C4 模型 的顺序来:

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

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


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

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

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

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


步骤 ⑦:找瓶颈,针对性扩展

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

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

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


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

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

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

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

面试里,主动暴露弱点是加分项而非减分项;工作中,这一步则是你三个月后回来 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。

🎯 随堂检验

🤔同事接到新需求后立刻画了 API + DB + Redis 三张框。按本章八步流程,他最可能跳过了哪一步?
  • A步骤 ⑤ 画高层架构
  • B步骤 ② 信封背面估规模——还没算 QPS、读写比、存储量就定了组件
  • C步骤 ⑧ 回顾取舍与风险

现在,轮到你了

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

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

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

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


相关链接

💬 评论