1. 为什么要维护一份自己的 system-design-notes
1.1 收藏过一堆文章,面试时却开不了口
先说我自己的经历。三年前我第一次准备系统设计相关的内容,干的第一件事就是去网上搜“系统设计入门”“System Design 面试攻略”,然后疯狂往收藏夹里丢链接。缓存、消息队列、分库分表、一致性哈希、CDN……每个名词我都觉得“我懂了”,每个概念我都能背出几句定义。
结果第一次模拟面试,对方让我“设计一个短链接系统”。我脑子直接空白了。
明明我昨天刚看过一篇讲短链接的文章,里面讲了 Base62、301 跳转、布隆过滤器、缓存分层,我记得清清楚楚。可真要我从头开始说,我却不知道从哪一句开口。是先讲需求?还是先画架构?还是先算数据量?我甚至不确定面试官到底想听什么。
那次模拟面试给了我一个非常深刻的教训:收藏不等于学会,看懂不等于能用。你看过的所有文章都是别人消化过的二手信息,它们有别人的框架、别人的取舍、别人的优先顺序。你把它们堆进收藏夹,就像把一堆半成品零件扔进仓库,平时觉得“我有存货”,真到用的时候根本找不到哪颗螺丝应该拧在哪。
1.2 笔记的价值不是“记录”,而是“重构”
后来我开始动手整理自己的 system-design-notes,才慢慢明白一个道理:真正改变你能力的,不是记了多少东西,而是你用什么方式去组织这些东西。
如果把系统设计比作做饭,网上那些文章是菜谱——告诉你放多少盐、炒几分钟。而整理笔记的过程,是你自己站在灶台前,亲手把菜切好、把锅烧热、把火候调对。你会开始追问:为什么先放蒜再放菜?为什么这步要大火?如果锅不够热会怎么样?这些追问,菜谱上不会写,只有你自己操作过、体验过、失败过,才能真正理解。
所以我这份 system-design-notes 从一开始就不是“抄笔记”,而是“重建一套自己的分析框架”。我不再记录“缓存是什么”,而是记录“在什么场景下、为了什么目标、用什么缓存方案、付出什么代价”。同一份素材,换个组织方式,价值天差地别。
这套笔记后来帮我顺利通过了几个重要的系统设计面试,也让我在日常工作中做技术方案时明显比以前有底气。如果你也想完整过一遍系统设计的核心脉络,下面这份笔记体系可以直接拿去参考。
2. 笔记的骨架:先有地图,再添细节
2.1 按问题清单组织,而不是按名词清单组织
大部分人做系统设计笔记,是按技术名词分类的:缓存一章、消息队列一章、负载均衡一章、数据库索引一章……每个名词下面摘录一堆“特性”和“优点”。这种笔记有一个很大的问题:它帮你记住了名词,却教不会你怎么用名词。
我自己的笔记换了一个组织方式——按问题分类。
我会在笔记开头放一张“问题地图”,大概长这样:
- 系统读多写少,怎么抗住高并发读?
- 系统写多读少,怎么处理写入压力?
- 数据量太大,单机放不下怎么办?
- 服务挂了,怎么保证可用性?
- 数据在多个副本之间,怎么保持一致?
- 用户分布在全国/全球,怎么做加速?
- 第三方服务变慢,怎么不让它拖垮主流程?
每个问题下面,再挂上对应的候选方案和取舍。比如“系统读多写少怎么抗”这个问题下面,我会挂上:缓存、CDN、读写分离、本地缓存 + 分布式缓存两级结构、静态化 / 预计算,等等。每个方案再附一段“适用场景”和“代价”说明。
这么组织的最大好处是,你在面试或做设计时拿到的不是一个一个名词,而是一整套“对照表”。面试官说“设计一个热点事件下的 feed 流”,你脑子里自动弹出“读多写少?用缓存套 CDN;关注关系?用粉丝列表拉取;热点流量?用预计算……” 你的思路是顺着问题走的,而不是拼命回忆“我缓存章节里记了什么”。
2.2 六步设计法:让每个系统题都走同一个流程
除了问题地图,我笔记里还有一条“主流程”。不管遇到什么系统设计题,我都会按这六步走:
- 需求澄清:先搞清楚这是读密集型还是写密集型?数据量级多大?一致性要求多高?需不需要实时性?把这些问清楚,方案其实已经出来一半了。
- 流量与存储估算:接口 QPS 大概多少?峰值是多少?数据存一年会产生多少条记录?占用多少存储?
- 数据模型设计:核心实体有哪些?关系怎么表达?主键怎么选?
- 核心流程推演:用户点一下,请求经过哪些组件?每个组件做什么事?数据流怎么走通?
- 关键技术选型与取舍:缓存用哪种?MQ 用哪个?数据库怎么分片?每个选择背后对应的代价是什么?
- 瓶颈与演进路径:当前方案在什么量级下会挂?明年量翻十倍,哪一部分需要先升级?怎么平滑演进?
这六步我写成了笔记里的一页固定模板。每次分析新系统时,我就按这个模板逐项填写。填了几次之后,整个流程已经变成肌肉记忆了。后来面试时就算遇到没准备过的题,我也能镇定地从这个框架开始逐步展开,而不是站在那儿挤牙膏。
3. 核心方案的推演逻辑:从约束到取舍
3.1 CAP 是起点,但别只会背结论
几乎每篇系统设计文章都会提 CAP 定理,但很多人对 CAP 的理解停留在“三选二”这个口诀上。我笔记里专门花了两页纸,把 CAP 拆开讲透。
我记下的关键理解是:分区容错性(P)在分布式系统里是必选项,不是一个选项。只要你的服务部署在多台机器上,网络分区就可能发生。所以在 CAP 里,真正需要做选择的是“当分区发生时,你保一致性还是保可用性”。
这个选择落到具体场景上是这样的:电商扣库存,宁可短暂拒绝用户也要保证不超卖,选 CP;发评论,用户刚发出去稍微延迟在其他端看到可以接受,但必须保证用户能发出去,选 AP;银行转账,必然 CP,不能出现账目不平。
笔记里不应该只写“CP / AP / CA 分别是什么”,而应该写“什么业务场景天然倾向 CP,什么场景倾向 AP,为什么”。面试官问 CAP,真正想听的不是定义,而是你能否在具体场景里做取舍。
3.2 缓存、消息队列、分库分表:什么时候该出场
我笔记里给每个核心组件都画了一张“触发场景表”,把“什么时候考虑它”写得很具体。
缓存的触发信号是:读量明显大于写量、数据变化不频繁、容忍一定延迟。如果数据每分钟变一次,但你允许用户看到最多几秒前的数据,那缓存就是第一选择。缓存层要考虑的问题也很固定:key 怎么设计、过期时间多长、缓存穿透/击穿/雪崩怎么防、缓存和数据库一致性怎么保。
消息队列的触发信号是:系统里有瞬时流量尖峰、上下游处理速度不匹配、多个下游需要消费同一份数据。引入 MQ 的核心收益是削峰填谷和异步解耦,但代价是链路变长、数据多一跳、消息可能会有延迟甚至丢失。笔记里要记录的是:你在哪个量级遇到的什么问题,才决定引入 MQ 的——这个问题比 MQ 本身更值钱。
数据库分片的触发信号是:单表数据量大到索引和查询性能明显下降、写入吞吐达到单机瓶颈。分片要考虑的细节最多:分片键怎么选才能均匀分布?跨分片查询怎么处理?数据迁移怎么做?分片之后还能不能 Global Secondary Index?这些不是背出来的,是在实际项目里被多次教育后沉淀出来的。
3.3 完整推演示例:短链接系统
拿我笔记里最常被翻的“短链接系统”来说,整套推演过程是这样的。
需求澄清:核心功能就两个,生成短链接和跳转。额外功能有自定义短链、过期时间、点击统计。约束条件:读多写少,典型比例大概 100:1。短链要尽量短、不可猜测。支持高并发访问,但不要求强一致——短链创建后延迟几秒内生效完全能接受。
流量与存储估算:假设每天新增 1 亿条长链接,那写入 QPS 大概是 1 亿 / 86400 ≈ 1157。跳转和创建是 100:1,读 QPS 就是 11.5 万,高峰期按 3 倍算约 35 万 QPS。存储方面,一条短链记录(短码 + 长链 + 创建时间 + 过期时间)算 500 字节,一年的数据量是 1 亿 × 365 × 500 ≈ 18.25TB。这个量级,单机数据库肯定扛不住,需要分片。
数据模型:核心表就是短链映射表,主键用短码。短码怎么生成?我用的是生成一个全局唯一 ID(雪花算法或号段模式),再转成 Base62 编码。6 位 Base62 有 620 亿种组合,足够用了。这个方案的好处是:无状态生成,不需要查重,天然支持水平扩展。
核心流程:用户提交长链之后,服务端生成唯一 ID,转成短码,写数据库,返回短链。用户访问短链时,网关根据短码做哈希找到对应分片,先查缓存,缓存没有再查数据库,拿到长链之后返回 301 跳转。这里的 301 是刻意选的,因为浏览器会缓存 301 结果,后续重复点击直接不走到我们服务器,大大减轻压力。
为什么数据要分片:一年 18TB 的数据,单机 MySQL 已经很难受了。分片键直接选择短码本身,因为查询时永远只用短码做等值查询,短码哈希散列之后分布均匀,不会出现热点分片。
瓶颈与演进:当前方案下,缓存可以扛住绝大部分读请求。如果读流量再翻十倍,无非是加缓存节点;如果短链生成量暴增,无非是加应用节点和数据库分片。这个方案最大的隐藏成本是“短码长度”和“分片数规划”——一开始分 256 片,后面要扩到 1024 片,数据迁移会非常痛苦,所以设计阶段就要多预留。
这一整套推演写下来,大概三千字。它不是抄来的,是我对着《System Design Interview》那本书,自己推了三遍之后写出来的。每推一遍都会发现之前忽略的细节,比如缓存穿透、比如短码生成要避免依赖数据库自增主键——这些细节的发现过程,才是整理笔记最大的收获。
4. 容量估算与性能参数:最容易丢分的硬通货
4.1 为什么一算数就垮
系统设计题里,面试官最常问的一个问题是:“你能大概估算一下这个系统的 QPS 和存储需求吗?”
很多人听到这个问题就懵。不是不会算,而是心里没有底——不知道一个普通活动页会被访问多少次,不知道一台数据库服务器大概能扛多少 QPS,不知道一个用户信息记录要占多少存储。你没法做估算,是因为脑子里缺少“基准数字”。
我笔记里用整整一章来整理这些基准数字,并且反复提醒自己:不要背这些数字,要用它们来做量级对比。
4.2 一张值得反复翻阅的性能参数表
以下是我笔记里保存的一张核心参数表,来源主要是各类公开的 benchmark 论文、工程博客和自己的实测数据,量级可信:
| 操作 | 延迟量级 | 备注 |
|---|---|---|
| CPU 寄存器 / L1 缓存访问 | ~1 ns | 速度的标尺 |
| 内存访问 | ~100 ns | 作为缓存时的命中成本 |
| SSD 随机读 | ~0.1 - 0.2 ms | 单机本地存储上限 |
| 网络同区访问 | ~0.5 - 2 ms | 微服务调用基础成本 |
| 数据库单行查询(含网络) | ~1 - 5 ms | 依赖索引命中情况 |
| 跨机房网络往返 | ~50 - 100 ms | 大规模分布式系统噪声来源 |
这张表最大的价值是:让你在方案之间快速做量级比较。比如,用户请求先查缓存(内存,0.1ms 量级)和直接查数据库(磁盘 + 网络,1-5ms 量级),两者差 10 到 50 倍。所以加缓存几乎是无脑优化。又比如,同机房 RPC 一次 1ms 量级,如果你设计的系统一次请求要串行调用 8 个服务,光内部调用就 8ms 起——这时候你该考虑并行调用或异步化了。
4.3 一次完整的估算过程:从日活到机器数
对应到前面的短链接系统,完整估算应该是这样的。
假设产品有 5000 万日活用户,其中 20% 的人每天会点开一个短链接,每人平均点 3 次,那么日跳转量就是 5000 万 × 20% × 3 = 3000 万次。平均 QPS 就是 3000 万 / 86400 ≈ 347。注意这是平均值,晚高峰通常是均值的三倍,我们按 1000 QPS 估算。
这 1000 QPS 需要多少机器?如果用 4 核 8G 的普通云主机,后端做好缓存分层,单机能扛 2000 - 5000 QPS 的简单查询接口。所以核心跳转服务 1 台机器就能扛住,甚至都不需要负载均衡。真正要把集群做大的原因是高可用,不是为了性能。从成本角度来说:预留 2 倍冗余,2 - 3 台应用节点 + 1 主 2 从的数据库,就足够支撑这个量级的系统。这个结论,和你直觉里“千万级日活必须上大规模集群”的预设完全不同——真实系统到了瓶颈,往往是缓存命中率掉下来或者某个慢查询拖垮了连接池,而不是节点太少。
4.4 关于存储估算,另一个常常翻车的地方
短链接一年 18TB 这个数字,换算一下就知道为什么必须分片:一台普通数据库服务器的可用存储通常 2 - 4TB,性能优化后最多放几亿行。18TB 意味着至少 6 - 9 台实例。所以“一年后必须分片”不是期望,是刚需。
如果你做的是带附件上传的服务,存储估算会直接把方案推到另一个体量,比如每条用户消息平均含 200KB 图片,日均 500 万条消息,一天就是 1TB 新增对象存储,一年 365TB。这种情况下,数据库存的只是元数据,文件必须进对象存储(OSS/S3 一类),而且热点内容一定要走 CDN。
笔记里我给自己设置了一个小习惯:任何系统题,先写估算再谈方案。因为方案必须跟着量级走——支撑每天 1 万次调用的接口,用最简单的单体 + 单库就够了,上全套微服务反而增加运维负担。
5. 面试场之外:笔记如何反哺日常开发
5.1 技术方案评审,本质就是一次系统设计推演
很多人以为系统设计只为面试准备,其实我后来发现,它在工作里才是真正的大杀器。
日常工作中,我们经常要写技术方案、做技术评审、评估别人的设计。这套笔记训练出来的思维,让我在评审别人的方案时更注意“约束条件”而不是直接跳到“选型对比”。比如同事说“我们用 Redis 做排行榜”,我会先问一句:“这个排行榜的读 QPS 是多少?数据量多大?一致性要求呢?Redis 挂了业务怎么兜底?”——这些全是笔记里反复训练过的问题。
做方案评审最怕的不是方案不够完美,而是设计者根本没有想过“这个方案在什么条件下成立”。系统设计笔记教会我的最重要的一件事,就是任何方案都要带着边界条件描述。比如“用 Redis 缓存,适用于缓存命中率 > 95% 且数据可容忍丢失的场景”,这种描述方式让评审瞬间轻松很多。
5.2 笔记里的方案帮我少走的两三个弯路
真实工作里,我几次按笔记思路做事,收获非常大。
一次是给一个老服务加缓存。旧逻辑是每次请求直接查 MySQL,线上高峰期 CPU 50%。按笔记里的“读多写少”框架,我加了 Redis 做二级缓存,命中率 97%,高峰期数据库 CPU 直接掉到 8%。全程没有改任何 SQL,只加了约 40 行缓存逻辑。这里的核心不是 Redis 本身,而是“先判断场景适配度,再动手”——如果换成一个写入频繁的业务,加缓存不但没用,还可能因为淘汰策略每天触发缓存雪崩。
另一次是帮朋友看一个 MongoDB 连接数打满的问题。页面每次请求会多次访问 MongoDB,他的第一反应是改代码做合并查询。我翻出笔记里的“性能参数表”,跟他算了一笔账:一个查询 1ms,每页 10 个查询串行就是 10ms,MongoDB 连接数高主要是因为连接池配置无上限而不是 QPS 高。把连接池上限从 1000 调到 100,加了本地缓存减少重复查询,问题当天就缓解了大半。这就是数量级的威力——先判断量级对不对,再决定优化哪个环节。
5.3 笔记成了团队里的轻量级参考手册
现在我的 system-design-notes 已经不只有自己在看,团队里有新人入职、或者同事要做一个新模块的初步设计时,我都会把笔记里对应的章节直接发给对方。它帮新人少踩了很多基础坑,比如在 MySQL 单表超过 1000 万行的时候考虑分库分表这种“默认前置思考”,从而避免把分片当成事后补救方案。
6. 让笔记活起来:迭代、纠错、复盘
6.1 三个月回看一次,重点看“我曾经错过的判断”
笔记如果不回看,很快会变成一座越来越乱的仓库。我的方法是每三个月至少回看一次,只看两个部分:容量估算表和系统设计模板。回看时不只是重温,更重要的任务是修正已经过时的判断。
举个例子,我最早的笔记里写“单表建议不要超过 2000 万数据”,后来自己实际操作发现这个值毫无意义——数据行大小完全不同,一个 100 字节的短行和一张几个 KB 的宽表,单表承载能力天差地别。所以后来我把“2000 万”改成了“控制在单个查询扫描的索引页数内,一般建议记录行总大小不超过 SSD 批量读写的合理范围,并留出日常备份、变更的余量”,这才是可以指导实践的标准。笔记里每一个如此修正过的判断,都是我真实进步的证据。
6.2 写笔记的三个原则:写原因、画图、留反方观点
我坚持这么多年整理笔记,沉淀出三个对自己有效的原则,也分享给你。
第一,只写结论背后的推理链条,不写孤立的结论。比如不能写“缓存适合读多写少”,要写“缓存适合读多写少,是因为它将热点数据的访问从磁盘网络链路提升到内存访问链路,两者相差一两个数量级;但一旦写入频繁或数据频繁失效,缓存带来的收益就会被失效和回源的代价抵消。”有了推理,你才能遇到新场景时自己推演,而不只是套结论。
第二,熟悉图,能自己画架构图和数据流图。系统设计题最怕的是“只可意会不可言传”的抽象描述。我在整理笔记时经常用文字描述一个链路,然后用在线画图工具把它转成架构图:客户端 → 负载均衡 → 应用服务 → 缓存 → 数据库 → 对象存储。每次画完都会发现一些逻辑漏掉的地方,比如日志、监控、限流器、幂等表里居然没接进来。所以如果你有精力,强烈建议每个核心方案都亲手画一张图。
第三,每个方案都要留一块“缺陷与反方观点”栏。比如我在“分库分表”章节旁边会记下:分片带来的跨分片查询、分布式事务、全局唯一 ID、数据迁移问题;在“消息队列异步化”旁边记下:消息乱序、重复消费、消息积压、出队吞吐瓶颈。这个“反方观点”栏,是让我在面试时能够跟面试官讨论 trade-off 的底气来源,也是我实际做技术决策时的安全提醒。
6.3 我的学习闭环:写笔记、找案例验证、再回来修正
你应该已经发现了,我这一整套笔记体系的背后,是一个循环:
- 读资料,吸收一个知识点,写进笔记;
- 在真实项目/模拟面试/案例复盘里用一次;
- 发现原来写的理解有偏差或者遗漏,回来修正笔记;
- 修正之后,对同一个知识点有了更深刻的理解;
- 继续读下一个知识点。
这个循环里最容易被忽略的是第二步“找案例验证”。如果你只是不停“读 + 写”,会陷入一种看起来很努力的假象,真实水平完全没有提升。一定逼自己输出,方式不重要——写技术方案、做分享、模拟面试、压测验证,都可以。我现在回想起来,我真正理解缓存穿透,不是在看布隆过滤器文章的时候,而是帮同事排查了一次因为空 key 导致数据库被打爆的问题,然后回到笔记里把“设置空值缓存期限”和“限流降级兜底”两个方案补了上去。
写在最后的个人心得
如果你也想拥有一份自己的 system-design-notes,我的建议是:别等准备好了再开始。我在 GitHub 上建仓库、写第一版笔记的过程,其实特别粗糙,目录很乱、表格也丑,还充满了从别人文章里抄来的复述。但就是那版粗糙的笔记,逼我把浮躁的知识点串成了自己的分析框架。之后每一次回看、每一次修正,都是在给这个框架加固。
还有一个小技巧:把笔记里的核心模板做成几页纸,面试前看模板而不是翻全部笔记。我的“六步设计法”和“性能参数表”就是这两页纸,它们让我在真正紧张的场景下也能保持清晰的思路,而不是被海量细节淹没。
系统设计不是什么天书,它只是一套“在资源有限的条件下,做合理取舍”的思维方式。一份真正属于自己的笔记,能帮你把这套思维方式固化下来。希望这份整理思路,对你有用。