系统设计笔记实战:从面试失败到架构决策框架
2026/9/15 7:41:21 网站建设 项目流程

大约三年前,我开始在 GitHub 上维护一套名为system-design-notes的系统设计笔记。起因特别狼狈:我连续两次挂在系统设计面试上,而且都是同一类问题——不是不会写代码,而是一到“请你设计一个XXX”的环节,脑袋就一片空白,聊不到十分钟就开始语无伦次。后来我逼着自己坐下来,把踩过的坑、看过的资料、面过的真题全部整理成一套结构化的笔记,再拿真实业务去验证,才慢慢想明白一件事:系统设计面试根本不是拼灵感,它是在考察一个人在面对不确定性时,能不能按一套可重复的决策方法做取舍,而且能不能把每个选择背后的理由说清楚。

这套笔记帮我解决的实际问题有三个:第一,在面试里建立“先问需求、再算容量、后谈组件”的稳定表达框架,不会说着说着就跑偏;第二,平常写技术方案时,能快速对照经典模型做决策,而不是凭感觉拍脑袋;第三,把脑子里零散的缓存、消息队列、分库分表、一致性知识点串成一张可复用的网。如果你正在准备系统设计面试,或是刚接手架构评审、需要在有限时间里输出靠谱方案的工程师,这套笔记的整理思路应该能直接给你参考。

1. 为什么要把系统设计做成一套笔记

1.1 从一次失败的面试说起

有一次面试官让我设计一个资讯 Feed 流系统。我当时脑子里全是 Kafka、Redis、推拉结合这些名词,上来就画了个架构图,把消息队列和缓存都堆上去。面试官听完平静地问了一句:“你预估这个系统需要支撑多少 QPS?读请求和写请求的比例是多少?你画的 Kafka 在哪个环节解决了哪个问题?”我当场愣住了。

那场面试之后我做了一次复盘,发现自己的核心问题不是知识量不够,而是没有一套“按顺序思考”的框架。系统设计面试的特点在于:问题本身是半开放的,面试官期待的不是唯一正确答案,而是你在信息不全、约束冲突的情况下做决策的能力。没有框架的人容易陷入细节,比如一上来就纠结数据库选型,却忘了先确认每秒多少请求、数据保留多久、是否需要强一致。这些信息一旦缺失,后面所有方案都是空中楼阁。

那次复盘也让我意识到,系统设计是可以被“刻意练习”的:把每一个经典问题都按照同样的结构拆解,把每次面试官追问记录成笔记,反复对照、反复重写,就能逐渐形成肌肉记忆。

1.2 笔记的定位:不是知识搬运,而是决策训练

普通的读书笔记是把别人说的话记下来,而 system-design-notes 更应该像一份“决策记录”。我在整理每一个专题时,固定用五个字段来约束自己:

字段记录内容为什么必须写
场景业务背景、功能需求没有场景的方案都是空谈
约束QPS、数据量、可用性要求决定方案的规模,先有数字再画图
选型候选组件和方案对比记录为什么选 A 而不是 B
取舍一致性、延迟、成本之间的权衡系统设计本质就是取舍
追问面试官/评审人会揪着哪里问提前想好薄弱点,避免当场卡壳

这套字段一开始写起来很痛苦,经常在“约束”一栏写不出数字,在“取舍”一栏写不出理由。但坚持十几道题之后,我发现每当面对新问题,脑子会自动弹出这些栏目:先问场景,再估数字,然后才谈技术组件。这也是我把笔记定位成“决策训练”而不是“知识收藏”的原因——收藏再多资料,如果不能在方案里用出来,那它对你的实际提升极其有限。

1.3 目录结构怎么划分

我的 system-design-notes 目录经历了三次大的调整,最终稳定成三大块,这个结构你直接抄也能用:

  • 基础篇:网络、存储、缓存、消息队列、计算与索引。每个基础组件都单独一页,写清楚原理、适用场景、瓶颈和常见坑。
  • 模式篇:高并发、高可用、一致性、容灾、安全。每个模式下面盖若干种落地手段,比如高并发底下有缓存、异步、水平扩展、分库分表。
  • 实战篇:短链接、Feed 流、秒杀系统、IM 聊天、分布式 ID、限流器等高频题目。每个实战题都严格按“需求澄清 → 容量估算 → 架构设计 → 存储设计 → 接口定义 → 扩展与追问”六段式来写。

我在目录里还维护了一个 README 表格,记录每个题目的完成状态、最后复习时间和被面试官问过的新增问题。这套文档最初只有我自己看,后来分享给团队,很多同事直接拿它当设计评审的参考清单用。目录结构本身也是一种知识管理方式:它逼着你在记笔记时就想清楚,这个知识点到底属于“原理”还是“模式”还是“案例”,归类本身就是在加深理解。

2. 系统设计的高频专题与核心原理拆解

2.1 容量估算:先学会算数,再谈架构

系统设计面试里最容易被低估的环节就是估算。很多人觉得这是小学数学,但其实面试官恰恰是通过估算来考察你有没有“数量级感”。数量级感的缺失会导致两种典型翻车:一种是任何系统你都上十台机器、上消息队列、上缓存,显得完全没有成本意识;另一种是设计出来的系统根本扛不住流量,比如给一台 MySQL 设计每秒十万次写入的方案,这是明显不现实的。

容量估算要抓三个关键数字:QPS(每秒查询数)、存储量、带宽。以短链接服务为例,假设日活跃用户 100 万,每个用户每天生成 1 条短链,峰值因子按 5 倍算:

生成短链的峰值 QPS ≈ 1,000,000 / 86,400 × 5 ≈ 58 QPS 假设每条短链平均被点击 10 次,跳转 QPS ≈ 58 × 10 ≈ 580 QPS 一年生成短链总量 = 100 万 × 365 = 3.65 亿条 每条记录约 100 字节,一年存储量 ≈ 3.65 亿 × 100B ≈ 36.5 GB

算完之后你会发现,这个系统的核心瓶颈根本不在 QPS,而在于如何生成不冲突的短码、如何处理点击统计,以及缓存策略。很多人一上来就堆中间件,本质上是因为没有先算这一笔账。估算的意义不是得到精确值,而是让你知道方案该用一台机器还是十台机器,是上关系型数据库还是引入缓存和消息队列。我建议把常见的参考数量级背下来:单台 MySQL 每秒支撑几千次简单查询,单机 Redis 每秒十万量级读写的处理能力,单台 Nginx 的并发连接数上限,这些数字能帮你快速锁定方案边界。

2.2 一致性模型与 CAP 的落地判断

系统设计面试绕不开“一致性”。但你如果对面试官说“我们要实现强一致”,措辞上可能暴露问题——大多数互联网系统在用户可感知的范围内用的是最终一致性。关键在于搞清楚:谁可以接受最终一致,谁能接受多强的一致。

举一个我常举的例子:点赞数和库存。点赞数如果稍微延迟几秒,用户刷新一下看到变化,通常没人觉得异常;但库存扣减一旦超卖,用户下单成功却发不了货,就是事故。两者对一致性的要求完全不同。所以在设计系统时,我会先把所有数据操作按“一致性敏感度”打标签:强一致场景(扣库存、转账)优先考虑数据库事务、分布式锁、或者具有原子操作能力的数据结构;最终一致场景(计数、Feed 流、通知)则可以用异步消息和解耦架构。

CAP 理论很多人背得滚瓜烂熟,但面试官真正关心的是能不能落地。实际工程里,网络分区无法彻底避免,所以通常在 AP 和 CP 之间做选择;哪怕选择了 AP,也可以通过“补偿机制”把系统往一致方向拉。比如异步对账、状态机重试、幂等设计,这些都是提升“最终一致”收敛速度的手段。我在笔记里专门留了一节记录“补偿方案清单”,每次面试聊到一致性,我会主动说出两到三种补偿手段,这比背一串 CAP 定义要打动人得多。

2.3 存储选型:SQL、NoSQL、对象存储、消息队列怎么挑

存储选型是系统设计里最容易被反复追问的环节。我的经验是不要背“某某数据库适合什么场景”,而是用四个问题去筛选:第一,需不需要事务和复杂 join?第二,读写比例多少,有没有明显的热点?第三,数据是结构化还是半结构化?第四,数据是可变记录还是不可变文件?

我总结过一个非常粗但好用的选型表格:

存储手段最适合解决的问题明显短板
MySQL/PostgreSQL强一致、事务、复杂查询水平扩展成本高
Redis/Memcached高并发读、缓存、计数存储成本高、持久化能力弱
MongoDB/Cassandra 等文档/列族半结构化、水平扩展事务能力和查询灵活性受限
对象存储图片、视频等不可变文件不支持频繁更新
消息队列异步解耦、削峰填谷不直接服务于在线查询请求

很多人会把消息队列也归类成“存储”,这其实是个值得注意的辨析点。消息队列本质是“流式数据管道”,它解决的是生产者和消费者之间的节奏不匹配问题,而不是持久化查询问题。面试里常犯的错误是:明明只需要一个延时很低的通知推送,却硬生生引入 Kafka,导致运维复杂度上升,收益却很低。我在笔记里会针对每个实战题,把候选存储列成一个表单,逐项打勾打叉,这样最后得出的方案说服力会强很多。

3. 把笔记转化成方案的实操路径

3.1 四步法:需求澄清、估算、组件选型、接口与细节

把知识转化成可落地方案,我的固定路径是四步法。第一步是需求澄清,这是整个系统设计的“定盘星”。你需要问清楚:这是面向 C 端还是 B 端?核心功能是什么?需不需要登录、推荐、搜索?预期的用户规模是多少?有没有明确的写入和读取比例?在真实面试场景中,面试官会故意给出比较模糊的描述,你不追问直接开始画图,基本等于主动放弃。

第二步是容量估算,也就是把需求翻译成数字。根据日活估算 QPS、存储量、带宽,再根据数字反推系统规模。这里的难点不是数学,而是对参考值的敏感度:每秒一千请求和每秒一百万请求,方案复杂度完全不在一个量级。

第三步是组件选型。按照存储、缓存、消息队列、计算框架依次过一遍,每一个候选方案都要写出一条选择和一条不选的理由。比如“选 Redis 做缓存,因为它读性能好且支持过期机制;不选 MySQL 直接扛读流量,因为磁盘 IO 撑不住这个量级”。面试官非常吃这一套——他们会觉得你不是在背方案,而是在对比之后做出的理性决策。

第四步是接口与细节设计。用 API 定义、数据表结构、核心流程的字段说明,把方案钉死。这个环节容易出现的问题是有人在前面画了一堆组件,最后却讲不清一条请求从客户端进来后到底经过了哪些节点。我建议每次设计完,都要把一条日志链路从头到尾串一遍:用户请求 → DNS/负载均衡 → 应用层 → 缓存 → 数据库 → 消息队列 → 返回结果。能完整讲清这条链,方案的可信度和说服力都会大幅上升。

3.2 用一个完整案例验证笔记:设计短链接服务

短链接几乎是所有系统设计笔记的标配题目,因为它麻雀虽小五脏俱全。拿我笔记里的短链接实战题来演示四步法。

需求澄清阶段先问:短链接用于什么场景?如果只用十天需要生成多少条?要不要支持自定义短码?要不要统计点击来源和 UV?这些直接决定方案复杂程度。假设需求是:支持普通用户将长链接转为短链接,他人访问时跳转到原始链接,提供点击量统计,默认链接永不失效。

容量估算阶段用前面算过的数字:生成 QPS 不到 100,跳转 QPS 约 600,一年存储几十 GB。这个规模说明系统不需要一上来就上大数据组件,MySQL 加 Redis 完全足够。

组件选型阶段,核心矛盾集中在短码生成算法上。我对比过三种方案:Hash 取前几位然后查重、全局发号器转 62 进制、预先批量生成短码并放入池中。全局发号器的好处是完全不会冲突,每次从发号器拿一个递增 ID 再转成 62 进制短码,缺点是发号器本身成为单点;预先发号是常见的优化手段之一,批量生成一批放到 Redis 里,应用层直接取池子里的码,性能很好,但要注意池子的大小和补充策略。选型评价要看两点:一是冲突处理方式是否明确,二是高并发下是否会成为瓶颈。

存储设计方面,我选择 MySQL 存储短码映射关系,Redis 缓存高频访问的映射。表结构大致如下:

CREATE TABLE short_url ( id BIGINT PRIMARY KEY AUTO_INCREMENT, short_code VARCHAR(16) NOT NULL UNIQUE, original_url TEXT NOT NULL, user_id BIGINT, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME NULL, INDEX idx_created_at (created_at) );

接口定义如下:

POST /api/v1/shorten Request: { "originalUrl": "https://example.com/very/long/path" } Response: { "shortCode": "abc123", "shortUrl": "https://s.example/abc123" } GET /{shortCode} Response: 301/302 跳转到 original_url

缓存策略用 Cache Aside 模式:先查 Redis,未命中再查 MySQL,回填缓存并设置过期时间。要防的三类问题是缓存穿透、缓存击穿、缓存雪崩,穿透可以靠布隆过滤器挡掉,热点数据过期导致的击穿通常建议用互斥锁或逻辑过期兜底,雪崩则依靠过期时间加随机值和多级缓存来缓解。最后再提一个不少面试官爱追问的细节:跳转用 301 还是 302?301 是被浏览器永久缓存,服务端无法统计后续点击;302 是临时重定向,每次都会经过服务端,利于点击统计。所以这类需求下选 302 更合理。

3.3 设计一个限流器:把笔记里的知识点连起来

限流器也是系统设计笔记里的经典题,它能把算法、并发、分布式、一致性多个知识点串起来。先说业务场景:一个 API 网关要保证每个用户在 1 秒内最多访问 10 次,超出的请求直接返回 429。这时候你需要先选算法——固定窗口、滑动窗口、令牌桶、漏桶之间怎么选?

我的笔记里对每个算法都做了对比:

算法实现复杂度是否平滑适用场景
固定窗口最低不平滑,边界有突刺低频接口、简单兜底
滑动窗口低到中较平滑,依赖粒度对毛刺敏感的接口
令牌桶平滑,允许一定突发网关限流、突发流量吸收
漏桶平滑,但拒绝对突发友好下游处理能力固定时保护下游

具体实现上,单机版本可以用本地内存加原子计数器或令牌桶;分布式版本则借助 Redis 的 Lua 脚本保证“读取-判断-扣减”整个流程的原子性。Redis 限流最常用的命令是 INCR 加 EXPIRE。每个用户一个 key:rate:limit:{userId},先 INCR,如果是第一次则设置 1 秒过期,判断当前值是否超过阈值 10,超过则拒绝请求。

这里面有一个非常容易被忽略的精度问题:Redis 按 key 粒度的 INCR 操作在高并发下是原子的,但多个副本之间的时钟同步会导致分布式限流的边界误差。大多数场景下,兜底限流不需要极致的精确,允许少量误差是可接受的。因此我在笔记里会明确写出“先定义业务上能接受的误差范围,再决定分布式限流的实现复杂度”。限流器这个题可以在十分钟内讲完,但它能带出你对并发、分布式和成本判断的多重理解,是典型的性价比极高的面试题。

4. 常见坑与排查实录

4.1 最容易翻车的估算环节

我复盘过很多次模拟面试,发现估算环节是所有候选人翻车的高发区,而且翻车的姿势非常集中。第一种是把 QPS 和并发数混为一谈。QPS 是每秒请求数,并发数是同一时刻系统内正在处理的请求数。一个系统 QPS 可能是一万,但并发数只有两百,因为每个请求平均处理时间只有 20 毫秒。你如果张口就说“并发一万”,面试官马上知道你概念不清。

第二种是拿不出任何参考数字。你说“这个系统需要上缓存”,面试官问“你判断的依据是什么?”如果你答不出来阈值,方案就缺少说服力。这时候记住几个基准值特别有帮助:单个 MySQL 实例在普通业务查询下大约能撑每秒几千次;Redis 单实例读操作可以达到每秒十万量级;网络带宽方面,25Mbps 大约每秒能传 3MB 左右的数据。这些数字不需要精准,它们的作用是帮你判断数量级。

第三种是忘了算峰值系数。日活百万不代表每秒平均请求就是十一;大多数系统有明显的流量高峰。我一般会在估算时默认乘以 3 到 10 的峰值因子,并对整体方案做预留,解释时明确说“日活 100 万、峰值因子 5”,面试官很吃这套,因为它体现了工程上对不确定性的准备。

我建议在笔记的“估算”小节固定维护一张常数量级表,每次做新题时都翻出来对照一遍,三个月后数量级感自然就有了。

4.2 缓存与数据库一致性:说错方向基本就凉了

缓存与数据库的一致性问题是系统设计面试里几乎必问的点,但真正能把它讲清楚的人不多。常见的错误回答是“更新数据库之后立刻更新缓存”,排查时容易遇到的坑在于:如果两个并发线程分别更新数据库和缓存,数据库更新的顺序和缓存更新的顺序可能完全相反,导致缓存里长期存着一份旧数据。我之前有一次真在这样的坑里踩了好几个小时。

更稳妥的做法是 Cache Aside 模式:读的时候先读缓存,没命中再读数据库,然后回填缓存;写的时候先更新数据库,然后删除缓存,下次读请求再重新加载。这种做法的核心逻辑是“让缓存成为可丢失的副本,从数据库这个唯一事实源回源”。但“先更新库再删缓存”也有一个隐患:如果删缓存失败,旧缓存依然存在。业界常用的优化方案之一是延迟双删,也就是先删一次缓存,更新数据库,过几百毫秒再删一次,把中间可能写入的旧缓存清掉。

需要特别说明的是,延迟双删也只是概率性降低冲突,不是万能药。如果你的业务真的要求强一致读,那缓存方案本身就值得重新考虑——要么直接短路缓存,让关键读请求走数据库;要么用带版本号的缓存,写入时带上数据版本,读时发现版本过期就回源。面试时把这几层说出来,给面试官的感觉会完全不一样:你不是在背答案,你是真的理解一致性问题背后有程度、有取舍。

4.3 笔记维护与复盘的方法

技术类笔记没有一劳永逸这种事,system-design-notes 的维护方式决定它到底是“死文档”还是“活工具”。我的习惯是给笔记建一个独立 Git 仓库,每做完一次模拟面试或真实面试,不管表现好坏,当天就把被追问的新问题追加到对应实战题的“追问”栏目里,提交一次 commit。这个动作会在几个月后形成一条清晰的成长轨迹,你能看到相同知识点从“看不懂”到“能解释”再到“能举反例”的完整过程。

每隔三个月我会彻底重写一篇实战题,不看旧笔记,从空白页开始重新推导整个设计。如果中途卡住,就说明对这块的理解还停留在记忆层,没有内化。重写不是复制,是重新思考。你会发现三个月前觉得合理的选择,现在可能想推翻重来,这恰恰说明头脑里的知识网络正在快速迭代。

另外我强烈建议用“讲解式”复盘代替“阅读式”复习。写笔记的人最了解内容,但讲给别人听时,能把话说得让一个初学者听懂才算真懂。我团队里有个同事每周五下午会拉我花 20 分钟听他讲一个系统设计题,讲完再一起挑毛病。这种方法比独自看笔记高效很多,因为它逼你把脑海里的隐性知识显性化。

5. 这套笔记后续还能怎么用

5.1 从面试工具变成团队内部设计评审清单

我最初做这套笔记只是为了面试,但后来它意外变成了我们团队做设计评审时的参考清单。以前技术方案评审经常变成自由聊天,想到哪说到哪,效率低且容易漏掉关键风险。后来我把 system-design-notes 里的“需求澄清 → 容量估算 → 组件选型 → 接口细节”提炼成一份评审检查问卷,评审会按顺序过一遍:需求里有没有明确量级?估算数字是否合理?有没有一致性方案?故障怎么恢复?监控告警怎么做?

这套流程执行半年后,效果非常明显:因为大家在方案阶段就被追问“量级和成本”,所以很多基础问题在评审前就自我过滤了。有一回新同学设计一个数据同步任务,直接说要上十台机器跑分布式调度,结果一问数据量每天只有几十万条,一台机器绰绰有余,最后把架构从分布式调度直接砍成了单机定时任务,部署和运维成本都降了一大截。这就是“先算账再拍方案”的价值,而笔记正是把所有算账方法固化下来的载体。

5.2 结合源码和线上故障继续补笔记

想让笔记始终保持生命力,就必须把它和真实世界的反馈连起来。我在基础篇里记录过很多 Redis 相关的知识点,后来线上出现过一次分布式锁失效导致的重复处理问题,排查之后发现是锁过期时间设置得过短,以及主从切换时锁副本尚未同步。我把这次故障的完整时间线、根因和修复方案追加到了 Redis 专题下面,从此再看到“分布式锁”这个标题,我的第一反应就不再是背 Redlock 算法,而是先追问锁的租约续期和主从容错问题。

线上故障是系统设计笔记最好的养料。今天你写在笔记里的每一个原理,如果不在真实业务中碰过壁、踩过坑,那它在关键时刻带给你的判断力其实是很有限的。反过来,当你把一次故障的根因写进对应专题,下次做方案时就会下意识地把这个场景带入,整体架构的鲁棒性会提升不止一个量级。

写这套笔记这几年,我最深的体会是:系统设计能力不是靠“看”出来的,而是靠一次次“选出来、讲出来、复盘出来”的。如果你也想整理一份属于自己的 system-design-notes,别急着收藏一大堆资料,先找一道最简单的题,比如设计一个短链接服务,拿出白纸先估算 QPS 和存储量,再画架构图,最后写接口和表结构。哪怕一周只做一道题,三个月后再回看,你都会发现自己判断方案的眼光明显不一样了——这个习惯,比笔记本身值钱得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询