尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

系统设计笔记实战:从面试失败到架构决策框架

系统设计笔记实战:从面试失败到架构决策框架 大约三年前我开始在 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 还是 302301 是被浏览器永久缓存服务端无法统计后续点击302 是临时重定向每次都会经过服务端利于点击统计。所以这类需求下选 302 更合理。3.3 设计一个限流器把笔记里的知识点连起来限流器也是系统设计笔记里的经典题它能把算法、并发、分布式、一致性多个知识点串起来。先说业务场景一个 API 网关要保证每个用户在 1 秒内最多访问 10 次超出的请求直接返回 429。这时候你需要先选算法——固定窗口、滑动窗口、令牌桶、漏桶之间怎么选我的笔记里对每个算法都做了对比算法实现复杂度是否平滑适用场景固定窗口最低不平滑边界有突刺低频接口、简单兜底滑动窗口低到中较平滑依赖粒度对毛刺敏感的接口令牌桶中平滑允许一定突发网关限流、突发流量吸收漏桶中平滑但拒绝对突发友好下游处理能力固定时保护下游具体实现上单机版本可以用本地内存加原子计数器或令牌桶分布式版本则借助 Redis 的 Lua 脚本保证“读取-判断-扣减”整个流程的原子性。Redis 限流最常用的命令是 INCR 加 EXPIRE。每个用户一个 keyrate: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 和存储量再画架构图最后写接口和表结构。哪怕一周只做一道题三个月后再回看你都会发现自己判断方案的眼光明显不一样了——这个习惯比笔记本身值钱得多。
返回列表