
写一套能直接拿来用的 system design 笔记是我做架构评审和面试准备这两件事交叉进行时慢慢养成的习惯。最开始只是想整理自己的知识盲区后来发现这套东西的用处远超预期它既能让你在系统设计面试里从“卡壳边缘”拉到“有条理地说完”也能让你回到日常工作时面对“这个模块要不要加缓存”“消息队列用在哪儿合适”这类决定不再拍脑袋。这篇文章就把我这套 system-design-notes 的核心框架、组件选型逻辑、一套完整的实操案例和踩坑记录一并摊开希望对正在准备面试、或者想建立系统设计知识体系的同学有帮助。系统设计这个词看起来很大但其实拆开来看就那么几件事先搞清楚你要解决什么问题再估算这个问题大概多大然后决定用哪些组件拼出方案最后把方案讲得别人能听懂、能落地。这四步中的每一步都有套路可循也有坑可踩。我的笔记一开始是东一篇西一篇记录后来迭代成了“方法论 组件库 案例库”的结构这次就把这部分内容完整梳理一遍尽量讲透不讲废话。1. 项目概述为什么系统设计值得单独建一份笔记1.1 这套笔记解决的核心问题系统设计和写业务代码完全是两种思考方式。写业务代码的时候你面对的是明确的需求和现成的框架大方向早就定好了你负责把每个接口、每个状态流转写对就行。系统设计不同它给你的往往就是一句很抽象的话比如“设计一个短链接服务”“设计一个打车派单系统”既没有明确的接口定义也没有指定的技术栈。你需要在几十分钟内从零到一把一个可扩展、可维护、能落地的架构方案拿出来还要给出一套能说服人的理由。我见过太多基础不错的同学栽在系统设计上不是因为他们不懂技术而是因为他们不知道从哪开口。有人上来就画架构图画到一半发现漏了关键组件有人一上来就聊具体技术细节结果面试官问他“这个方案要支撑多大流量”的时候愣住了。这些问题的根源不是能力不够而是缺少一套结构化的设计流程。我这份笔记的首要价值就是把“系统设计到底应该按照什么顺序做”这件事固定下来让你在不同题目之间能复制的不是答案本身而是思考的路径。1.2 为什么系统设计面试和日常开发都离不开组件决策系统设计面试很少要求你发明新东西绝大多数题目考的都是现有成熟组件的组合能力。负载均衡怎么选、缓存放在哪一层、消息队列是解耦还是削峰、数据库到底该用关系型还是非关系型这些决策在真实的分布式系统里每天都在发生面试只是把它们压缩到一个小时之内。所以你会发现准备系统设计面试的过程本质上就是在为自己建立一套高密度的技术决策知识库而这套知识库在平时做方案评审、做性能优化的时候一样能派上大用场。我的笔记就是把这么散落的组件决策逻辑整理成了一份可以反复翻阅的资料。1.3 这份笔记适合谁看我把这份笔记的使用场景总结成三句话准备高端岗位面试的人拿它当备赛手册刚入行两三年、开始接触模块设计的工程师拿它当知识框架带小团队、需要经常做技术决策的人拿它当自查清单。如果你正处在任意一个阶段这套内容都能给你提供一套可以落地的思路。如果你只是想了解一下大厂的高并发系统是怎么设计的顺着这套框架读下来也能建立比较完整的认知。它不是一份单纯记录“是什么”的资料核心在于教你“怎么做”以及“为什么要这样做”。所以不管你是为了面试突击还是想补上系统设计这块短板这套笔记的学习成本都算低的但收益周期很长。2. 方法论拆解系统设计的五步设计法2.1 需求澄清先把问题定准再谈方案几乎每一次我帮别人模拟面试都会反复强调同一句话不要跳过需求澄清阶段的。这不是套话而是系统设计最关键的兜底动作。面试官抛出来的题目往往很宽泛宽到“设计一个XX系统”本身没有意义你必须通过提问把它收敛成一个可设计的问题。我会建议从四个维度去问一是核心功能也就是用户能做什么二是非功能需求也就是我们关心的QPS、延迟、可用性指标三是数据规模包括存量数据和增长趋势四是约束条件比如团队规模、成本、部署环境的限制。举个具体的例子如果题目是设计短链接服务你需要确认的问题大致是生成和跳转这两个接口哪个是核心跳转的平均延迟要求是多少预计每天新增多少条短链要不要支持自定义短链需不需要统计点击次数这些问题问完设计目标就已经清楚一半了。面试官并不会因为你问问题觉得你水平低恰恰相反会问问题说明你在有意识地控制设计范围而不是一头扎进方案里。2.2 容量估算用一张纸算清楚系统要扛多少压力容量估算是很多人的薄弱环节因为它涉及一堆数字而数字是最能暴露思路不清晰的地方。但容量估算其实不需要很高深的数学知识核心就两个动作先设定一个用户规模基数再做一下单位换算。我一直用的参考基数是日活跃用户数DAU比如假设DAU是1亿平均每个用户每天使用系统10次那一天的请求总量就是10亿如果这些请求集中在4个小时的有效时间窗口内那每秒大概是10亿除以14400秒约等于7万QPS。不要小看这种粗糙的估算它能帮你在设计的第一分钟就建立一个量级概念。有了量级概念之后系统设计的很多决策就开始自动浮出水面了单机QPS撑到1万都算不错所以如果估算出来是7万QPS你就知道必须要做集群化、负载均衡和缓存如果估算出来只有几百QPS那很多复杂的架构根本没必要上单体应用加一个读写分离可能就够用了。容量估算的最大价值不是精确而是帮你判断复杂度的温度——它高还是低决定了你接下来要做的是减配还是加码。2.3 接口设计与数据模型先定义契约还是先画架构我见过不少同学一上来就画整个系统架构的框图把网关、微服务、集群全画出来看起来很完整但面试官追问一句“那你的核心接口长什么样”就马上露怯。我更习惯的顺序是先定义核心接口再设计数据模型最后才是整体架构图。接口是系统对外的契约它决定了你能响应什么数据模型决定了系统能不能高效地支撑这个响应。这两件事没定下来架构图画得再漂亮也是空中楼阁。接口设计时要抓住最核心的两个场景不要贪多。继续以短链接服务为例核心接口其实就两个一个是生成短链的接口入参是原始URL出参是短码另一个是跳转的接口入参是短码出参是一次302重定向到原始URL。数据模型上短链映射表需要一个自增ID、一个短码字段、一个原始URL字段、一个创建时间字段再加一个过期时间字段。就这么一张表已经能支撑起整个服务的核心功能了。先把这个最小闭环定义清楚后面再谈扩展就顺理成章。2.4 从方案到表达设计好的系统也要讲得好说一个比较容易被忽略的点系统设计是做出来的也是讲出来的。面试官在有限的时间里能接收到的信息总量是有限的你的表达结构直接决定了他对你的评价。我的经验是方案表达也要结构化先一句话说明整体思路再用容量估算的数据作为决策依据然后分模块说清楚关键组件的作用最后抛出一个可以讨论的取舍点或者演进方向。这样给面试官的感觉是你不但能把方案做出来还能把方案讲清楚这在真实的架构评审中是比技术本身更稀缺的能力。实际说话的节奏上每个模块都遵循“设计目标→技术选型→关键机制”的结构不要在一个细节上纠缠太久。记得有一次模拟面试一个同学设计了一个很完整的Feed流系统但他的表达方式是平铺直叙地介绍每个组件讲完负载均衡讲MySQL讲完MySQL讲Redis面试官全程没有听到一次“我这个方案的目标是什么”。这种表达方式会极大削弱方案的冲击力。我笔记里专门记了一条每次开口讲设计之前先说明你在解决什么问题再谈你怎么解决。3. 核心组件选型高并发场景的四件套3.1 负载均衡流量入口的第一道闸门流量只要稍微大一点负载均衡就是绕不开的组件。它的职责很简单把请求分发到多台后端服务器上让每个节点都能稳定地扛住自己的份额同时在一台机器挂掉的时候能自动把流量摘除。选型上L4的负载均衡如LVS工作在传输层转发效率极高但只能基于IP和端口做分发L7的负载均衡如Nginx、HAProxy工作在应用层可以按URL、Header、Cookie做更细粒度的分发多用于HTTP和HTTPS流量的治理。分发算法上最基础的Round Robin轮询适合各后端性能相近的场景Weighted Round Robin适合后端机器规格不同、需要按权重分配的场景Least Connections适合请求处理时长差异比较大的场景IP Hash能保证同一IP的请求总是落到同一台机器适合需要会话保持的服务。我这里会记一个经验不要迷信某个算法的独特性大多数系统里带权重的最少连接算法Weighted Least Connections已经能解决90%的问题。3.2 缓存层为什么说缓存是性能的第一解药缓存的本质是用一份额外的存储空间去换一次网络或数据库访问的开销。加缓存需要考虑的是四个问题缓存什么、缓存多久、放在哪里、缓存失效了怎么办。最常见的选择是把热点数据缓存到Redis这类内存型存储中设置了过期时间用LRU或LFU这样的淘汰策略来限制缓存大小。但加了缓存之后所有技术人都应该警惕的三个经典问题是缓存穿透、缓存击穿、缓存雪崩。穿透说白了就是缓存和数据库里都没有这条数据请求每次都直接打到数据库上等于缓存白加了。应对办法是布隆过滤器或者缓存空值。击穿是指一个热点key的缓存突然失效瞬时大量请求直接命中数据库。应对办法是热点key不过期或者用互斥锁重建缓存。雪崩是指大量缓存key在同一个时间点失效数据库短时间内压力陡增。应对办法是把过期时间打散加一个随机值避免集体失效。在设计时遇到过这几个问题才能说缓存这层你真的想清楚了。3.3 消息队列解耦、削峰、异步但别乱用消息队列在系统设计里出现频率很高但我一直提醒大家消息队列不是不要钱的面包它带来了解耦也引入了复杂性。它的经典使用场景有三个一是解耦上游只管发消息下游自己去订阅两边互不依赖二是削峰面对突刺型的高流量先把消息放进队列让下游消费者按照自己的节奏处理三是异步化比如用户下单之后先把订单状态写成功再异步发送短信通知这样用户体感更快。选型上具体技术选型因场景差异而不同。延迟要求极高的即时消息场景和需要持久化和顺序消息保障的场景选择的队列是完全不同的。设计时关键要讲清楚的是消费模型和异常处理如果消费者挂了消息在队列里积压怎么办如果消费成功但确认消息丢失怎么办这些问题比“用什么消息队列”更能体现设计的成熟度。我笔记里有一条提醒只有当你真正需要解耦、削峰或异步时才引入消息队列否则用同步调用更简单可靠。3.4 数据库选型关系型与NoSQL的边界在哪里数据库选型是系统设计里最需要功底的部分之一。我的判断标准通常基于数据关系和访问模式。如果数据之间有复杂的关系需要事务支持有大量按条件查询的需求那关系型数据库如MySQL、PostgreSQL是首选如果数据量大、访问模式比较固定key-value按ID查询居多且不太需要跨表关联时NoSQL如Redis、MongoDB、Cassandra往往更合适。我把这个决策逻辑做成了一张速查表维度关系型数据库NoSQL数据库数据关系强关系适合多表关联弱关系一般以单个文档或key-value为主事务支持ACID事务完备大多只支持最终一致性或单文档事务扩展方式以读写分离、分库分表为主天然支持分布式横向扩展典型场景订单系统、用户系统、财务系统缓存、会话管理、海量日志、推荐数据存储模型表结构schema固定灵活模式文档/列族/key-value都有这里面有一个容易被忽略的点数据库选型从来不是非黑即白很多系统是混合使用的。比如核心订单数据落在MySQL热点商品信息放在Redis用户行为日志存入Elasticsearch。方案优雅与否取决于你有没有为每种数据选择最合适的载体。4. 案例实战从零设计一个短链接服务4.1 第一步明确需求与估算规模我们拿短链接服务这个经典题目把前面那套方法论完整走一遍。假设面试官只说了一句“设计一个短链接系统”你需要先問清楚需求。确认核心功能有两个把一个长URL压缩成短URL用户访问短URL时302跳转到原网址。再确认非功能需求跳转延迟要低于100毫秒预估日新增短链100万条系统运行三年后短链总量约10亿条跳转请求的峰值QPS约5万。拿到这些数字之后容量估算就很清晰了。10亿条短链的映射关系对应关系型数据库里大概需要几十GB的存储空间完全可以通过MySQL单库缓存支撑。5万QPS的跳转请求如果全部打到数据库上单库完全扛不住但在Redis缓存命中率90%以上的前提下打到数据库的量只有5000QPS分库分表之前单主库加从库集群已经可以稳稳扛住。这套数字立刻告诉我们这个系统不需要过于复杂的架构但缓存层是必须的。4.2 第二步设计API与数据模型先画最小闭环接口设计紧扣两个核心场景。生成短链的接口收到长URL后生成唯一短码返回跳转接口在收到短码后查到原始URL并302重定向。这两个接口本身很简单但生成短码的方案可以展开很多讨论。我比较推荐的做法是使用全局发号器比如数据库自增ID或者分布式ID生成器拿到十进制ID之后转换为62进制字符串作为短码。5位62进制能表示9亿个组合6位能表示560亿对当前数据规模来说6位短码是合适的选择。数据模型上一张短链映射表就够自增ID是唯一标识短码列建立唯一索引原始URL列存储长链接创建时间和过期时间列分别记录生成时间和有效期。核心跳转链路是用户请求短码先查Redis缓存如果有则直接返回长URL如果缓存没命中再查数据库返回长URL并把结果写回缓存如果数据库也没有则返回404。这条链路虽然简单但它囊括了缓存、存储、回源写入三个关键操作是整套系统的主心骨。4.3 第三步扩展性演进从单机到集群的平滑过渡任何系统都不是一天长大的短链接服务也不例外。这套设计的好处是它留好了扩展台阶。初期流量小的时候单台Nginx加一台MySQL加一台Redis已经完全够用。当跳转QPS持续上涨就把Nginx升级成负载均衡集群后端多挂几台无状态的应用服务器数据库做一主多从、读写分离Redis从单机切换到哨兵集群模式。再往后如果数据量接近单库瓶颈可以用短码做分片键把数据水平拆分到多个数据库实例里。扩展过程中还有两个需要提前规划的细节一个是发号器的高可用因为发号器一旦挂掉全链路的短码生成都会失败所以生产环境要用带容灾的多节点发号方案另一个是短码的过期与清理策略长期不访问的短码要有巡检删除机制避免无效数据持续占用缓存和数据库空间。把这些演进路径想清楚面试官看到的就不再只是一个静态草图而是一个有生命力、能随着流量一起成长的设计。5. 实操中的常见问题与排查技巧5.1 五个让我栽过跟头的设计误区第一个误区是过度设计。一个只有几千QPS的系统上来就上微服务、上分布式事务这不仅是浪费还会让系统复杂到无人能维护。第二个误区是忽略缓存一致性。加了缓存之后数据库更新了但缓存还是旧值导致用户读到过期数据。这个问题在设计阶段就要给出方案比如先更新数据库再删除缓存或者使用带版本号的双删策略。第三个误区是低估数据量。有的设计看上去很完美但把数据增长算进去之后存储方案和索引设计都需要推翻重来。第四个误区是忘记可用性设计只关心正常流程不关心流量高峰、机房故障、网络抖动时的表现。第五个误区是表达没有重点把大量时间花在细节上反而让主要决策淹没在信息里。5.2 拿什么工具验证你的设计是否靠谱设计做完之后我习惯用三个视角来复盘。第一是用容量数据来反推如果我现在把QPS提升十倍每个环节会不会出现瓶颈瓶颈会最先出现在哪里第二是用故障模拟来排查假如缓存集群整体挂了数据库能扛多久假如消息队列积压了十万条会不会影响核心链路第三是用扩展性视角审视如果业务方要求新增一个功能模块现有架构要不要伤筋动骨这三个问题回答得越流畅方案的质量就越有保障。日常积累的时候我也会关注一些公开的技术博客和大厂架构分享用来补充自己的组件库。但我的经验是不要贪多不要追求面面俱到每个组件记住三件事就行它解决什么问题、它的核心机制、它的局限性是什么。记住这三件事你在设计时就能做出比“我知道这个名词”更靠谱的决策。5.3 笔记怎么用才能发挥最大价值最后说点实际的。system-design-notes这类内容不是读过一遍就完事的。我自己的做法是每个周末挑一个经典题目短链接、Feed流、秒杀系统、聊天系统、网盘系统按照这套方法论完整走一遍设计流程然后对照笔记找差异。一开始你可能需要两个小时才能走完一个题目但练到第五个、第六个你会明显感觉到自己的思考路径开始自动化了——需求问完容量估算完脑子里自动开始排组件出场顺序。这个感觉一旦出现你离掌握系统设计就不远了。我在实际使用中还有一个体会设计系统的时候不要追求让每个部分都完美那是做不到的。真正好的系统设计是在一堆约束条件里找到那个“够用、可扩展、可维护”的平衡点然后明确说出“我在这里做了什么取舍为什么这样取舍”。能把这个逻辑表达清楚不管是面试还是真实项目你都已经赢过了大多数人。最后再分享一个我个人的小技巧把每次设计练习画在一张A4纸上画完拍照存进自己的笔记库里过两周再看一遍。你一定会发现当初觉得无懈可击的方案其实有不少可以改进的地方。这种回看和迭代才是让系统设计能力真正沉淀下来、不断成长的方式。