1. 这不是笔记,是系统设计能力的显微镜
“system-design-notes”这个标题乍看平平无奇,像极了GitHub上成千上万份被star过又沉底的仓库名。但如果你正站在准备System Design Interview的路口——无论是刚刷完200道LeetCode、手写过三遍LRU缓存、却在面试官问出“如果Twitter要支持每秒50万条推文,你会怎么设计?”时突然失语;或是已经工作三年、能独立交付模块、却在参与架构评审时听不懂“服务分层”“读写分离”“一致性哈希”这些词背后的取舍逻辑——那这份notes就不是随手记下的碎片,而是一面显微镜:它把抽象的“系统设计”四个字,拆解成可触摸、可验证、可复盘的肌肉记忆。
我见过太多人把System Design当成玄学。有人死记硬背“CAP定理三选二”,却说不清为什么ZooKeeper选CP而Eureka选AP;有人熟练画出微服务架构图,但当面试官追问“服务间调用失败率突然从0.1%飙升到5%,你第一步查什么?”,立刻卡壳;还有人花三个月啃完《Designing Data-Intensive Applications》,合上书却连一个简单的短链生成系统都画不出核心组件间的流量走向。问题不在努力,而在输入和输出之间缺了一座桥——这座桥不是理论堆砌,而是把每个设计决策背后的真实约束、权衡代价、落地陷阱,用最朴素的语言钉在具体场景里。这份notes的全部价值,就藏在“notes”这个词的本义中:它不是教科书,不是PPT,而是我在真实项目里踩坑后、在模拟面试中被拷问后、在深夜复盘线上故障后,一笔一划写下的“当时为什么这么选”“后来发现哪里错了”“下次一定先验证什么”。比如,为什么在日志聚合系统里,Kafka的分区数不能简单设为CPU核数的两倍?为什么用Redis做分布式锁时,SETNX加EXPIRE的组合在高并发下会失效?这些答案,从来不在官方文档的“最佳实践”章节里,而在你亲手部署、压测、观察监控指标的过程中。所以,当你打开这份notes,别把它当知识库去检索,而要当成一份带注释的手术记录——每一条笔记,都对应着一次真实的系统切口、一次血淋淋的权衡、一次事后诸葛亮式的清醒。
2. 从“画图”到“决策”:系统设计的本质是约束求解
很多人误以为系统设计面试就是考画图能力:画个方框代表用户,箭头连到API Gateway,再连到一堆Service,最后接个Database——图很完整,但离合格还差十万八千里。真正的系统设计,本质是一场在多重硬性约束下的动态求解过程。它不追求“完美架构”,只追求“在当前约束下最不坏的选择”。而这份notes的核心骨架,正是围绕四类不可妥协的硬约束展开:规模(Scale)、可靠性(Reliability)、延迟(Latency)、成本(Cost)。这四个字母,构成了所有设计决策的坐标系原点。
2.1 规模:不是“大”,而是“增长路径”的具象化
“支持千万级用户”这种描述毫无意义。真正关键的是:用户量、请求量、数据量的增长曲线是什么?峰值出现在什么时间?增长是线性的、指数的,还是脉冲式的?我在做电商秒杀系统时,曾天真地按日常QPS的10倍预估峰值,结果活动开始3秒内,订单创建接口的TPS就突破了预估上限的3倍——因为用户行为不是均匀分布,而是集中在开抢瞬间的毫秒级脉冲。这份notes里所有关于“水平扩展”的讨论,都始于对增长模式的量化拆解。比如,计算数据库分片数,绝不是拍脑袋定8个或16个,而是基于三个数字:单实例最大连接数(如MySQL默认151)、单实例安全QPS上限(实测值,非理论值)、预估峰值QPS。公式很简单:分片数 = ceil(预估峰值QPS / 单实例安全QPS)。但难点在于获取“单实例安全QPS”——这必须通过真实压测得出,且要覆盖慢查询、连接池耗尽、磁盘IO瓶颈等不同维度。我吃过亏:第一次压测只关注了CPU和内存,上线后发现高峰期大量请求因磁盘IO等待超时,原因竟是日志写入和业务查询争抢同一块SSD。所以notes里明确标注:“压测必须包含混合负载,至少包含70%读+20%写+10%日志写入”。
2.2 可靠性:99.9%不是目标,而是成本与体验的平衡点
“三个9”(99.9%可用性)意味着每年允许宕机约8.76小时。但这个数字背后,是无数个具体决策:是否引入异地多活?是否为每个核心服务配置熔断降级?是否对所有外部依赖做超时和重试?这些选择没有标准答案,只有成本核算。例如,为支付服务实现同城双活,需要双倍的服务器、数据库、网络带宽,以及复杂的流量调度和数据同步机制。但若支付服务宕机1小时,公司损失可能远超一年的双活建设成本。而对一个内部运营报表系统,99.5%的可用性可能已足够——因为它的用户可以接受“今天报表晚点出来”。这份notes的价值,在于把每个可靠性方案的成本具象化。比如,“引入消息队列解耦”这一常见建议,notes里会写明:Kafka集群的运维复杂度提升30%,消息积压排查时间增加50%,但能将下游服务故障导致上游超时的概率从100%降至<0.1%。这种量化对比,才是决策依据。我见过最典型的错误,是把“用了Kafka”等同于“高可靠”,却忽略了Kafka本身也是单点故障源——如果Kafka集群挂了,整个异步链路就瘫痪了。所以notes里专门有一节叫“Kafka的可靠性补丁”,列出必须做的三件事:1)设置min.insync.replicas=2并确保replication.factor>=3;2)消费者组启用enable.auto.commit=false,手动控制offset提交时机;3)为Kafka集群配置独立的监控告警,而非仅依赖应用层埋点。
2.3 延迟:毫秒级的战争,发生在每一跳网络与每一次磁盘寻道之间
用户感知的“慢”,往往不是某个环节的绝对延迟,而是多个微小延迟的叠加。一个典型的Web请求链路:DNS解析(20-100ms)→ TCP三次握手(10-50ms)→ TLS握手(50-200ms)→ HTTP请求发送(1-5ms)→ 服务端处理(10-100ms)→ 数据库查询(5-50ms)→ 网络传输返回(10-50ms)。即使每个环节都“很快”,总延迟也可能轻松突破300ms——而研究表明,用户等待超过250ms就会产生明显挫败感。这份notes里,所有关于缓存、CDN、连接池的讨论,都紧扣“如何砍掉哪一跳”。比如,为什么Redis缓存要设置TTL?表面看是防止数据过期,深层原因是避免缓存雪崩——当大量key在同一时刻过期,请求会瞬间打穿缓存直击DB。但更隐蔽的问题是:TTL设置过长,会导致缓存与DB数据不一致的时间窗口变大;设置过短,又会频繁触发缓存穿透。notes给出的实操解法是“双TTL策略”:主缓存(如商品详情)用较长TTL(如2小时),同时维护一个短TTL的“探针key”(如item:123:stale,TTL=5分钟),每次读取主缓存前先检查探针key是否存在,若不存在则异步刷新主缓存并重置探针key。这个方案把一致性窗口从2小时压缩到5分钟,且完全规避了雪崩风险。它不是教科书里的标准答案,而是我在一个实时价格系统里,为解决“价格更新后用户看到旧价长达2小时”的投诉,熬了两个通宵调试出来的。
2.4 成本:钱不是万能的,但没钱是万万不能的
技术人常陷入“技术洁癖”,认为只要方案最优,成本自然合理。现实恰恰相反:成本往往是第一约束。我参与过一个视频转码服务的设计,团队最初方案是自建GPU集群,理由是“完全可控、性能最优”。但财务同事拉出一张表:一台A100 GPU服务器月租3.2万元,按峰值需求需12台,年成本近460万;而使用云厂商的Serverless转码服务,按实际转码时长计费,预估年成本仅80万,且无需运维GPU驱动和CUDA版本兼容问题。最终我们选择了后者,并把省下的380万投入到提升转码算法效率上——这才是成本驱动的技术优化。这份notes里,所有架构图都附带一张“成本影响矩阵表”,横向是方案选项(如自建Redis vs 云Redis),纵向是成本维度(硬件采购、带宽费用、人力运维、故障恢复时间)。例如,对比“数据库读写分离”和“数据库分库分表”:前者初期成本低(只需加一台从库),但扩展性有限(从库数量受主库复制压力限制);后者初期成本高(需改造分片逻辑、引入Sharding中间件),但长期扩展性好。notes的结论不是“选哪个”,而是“当你的读QPS超过5000且持续增长时,读写分离的边际收益开始递减,此时应启动分库分表评估”。这种基于数据阈值的决策点,比任何主观判断都可靠。
3. 从“概念”到“代码”:把设计决策翻译成可执行的验证清单
系统设计最容易被忽略的环节,是“设计完成之后做什么”。很多人的笔记停在画完架构图就结束了,仿佛图一画,系统就自动跑起来了。但现实是,每一个方框、每一条连线,背后都藏着数十个需要验证的细节。这份notes最硬核的部分,就是把每个设计决策,翻译成一份可逐项打钩的“验证清单”。它不告诉你“应该怎么做”,而是逼你回答“你确认做到了吗?”。
3.1 API网关层:不只是路由,更是流量的守门人
API网关常被简化为“统一入口”,但它的核心价值是流量治理。这份notes里,针对API网关的验证清单包含12项,其中7项直接关联线上稳定性。例如,“限流策略是否区分用户级别?”——这是血泪教训。我们曾为登录接口设置全局QPS限流1000,结果一个恶意脚本用100个账号轮询,瞬间打满限流阈值,导致所有正常用户无法登录。正确做法是:对登录接口实施“用户ID维度限流”(如单用户每分钟最多5次),再叠加“IP维度限流”(如单IP每分钟最多100次)。notes里给出了Nginx+Lua的实现片段:
# 基于用户ID的限流(需从JWT解析user_id) limit_req zone=user_limit burst=5 nodelay; # 基于IP的限流 limit_req zone=ip_limit burst=100 nodelay;另一个关键项是“超时时间是否分级设置?”。很多团队给所有后端服务设置统一超时(如3s),但这是灾难。支付回调必须严格保证最终一致性,超时应设为30s以上;而搜索建议接口,200ms无响应就该返回空建议。notes要求:每个上游服务的超时时间,必须在网关配置中显式声明,并与该服务的P99延迟对齐。我们曾因未分级超时,导致一个P99=800ms的推荐服务拖垮了整个首页,因为网关等它3s才放弃。
3.2 数据库层:ACID不是银弹,隔离级别是把双刃剑
数据库设计笔记里,最常被误解的是事务隔离级别。很多人死记“READ COMMITTED防止脏读”,却不知道在高并发下,它可能导致“不可重复读”,进而引发业务逻辑错乱。例如,库存扣减场景:事务A读取商品库存为100,事务B在此期间扣减1件并提交,事务A再次读取时库存变为99——如果A的业务逻辑是“库存>50才发优惠券”,两次读取结果不同,就会导致本不该发券的用户收到券。这份notes的验证清单强制要求:每个涉及资金、库存、状态变更的核心事务,必须明确标注其隔离级别,并通过SQL注入测试验证效果。测试方法很简单:开启两个MySQL客户端,一个执行SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;,另一个执行SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;,然后在相同数据上执行读-改-读操作,观察结果差异。notes还特别提醒:“不要盲目升级到SERIALIZABLE,它会极大降低并发性能。优先考虑应用层加锁或乐观锁(version字段)”。
3.3 缓存层:穿透、雪崩、击穿,不是名词,是待解决的故障单
缓存相关笔记,我花了最多篇幅写“如何证明你的缓存方案真的有效”。验证清单第一条就是:“缓存命中率是否达到预期?”。很多人只看Redis的keyspace_hits指标,却忽略了应用层缓存(如Caffeine)的命中率。我们曾遇到一个诡异问题:Redis命中率95%,但整体接口P95延迟反而升高了——排查发现,应用层本地缓存因GC频繁被清空,导致大量请求穿透到Redis,增加了序列化/反序列化开销。因此notes要求:必须同时监控三级缓存命中率(本地缓存、Redis、DB),并计算“穿透率”(DB请求数/总请求数)。第二条是:“缓存更新策略是否原子化?”。常见的“先删缓存再更新DB”有风险:若删缓存成功,DB更新失败,缓存为空,DB有脏数据,下次读取就会把脏数据写回缓存。notes推荐“更新DB成功后,再异步删除缓存”,并提供RocketMQ事务消息的实现模板,确保DB更新和缓存删除的最终一致性。
3.4 消息队列层:消息不是发出去就完了,而是要“送达且仅送达一次”
消息队列的验证清单,核心是“消息语义”。很多团队只关注“消息是否能发出去”,却忽视“消息是否被正确消费”。notes里最关键的验证项是:“消费者是否幂等?”。我们曾因订单创建消息被重复消费,导致同一个订单生成了多张支付单。根本原因在于消费者逻辑未做幂等校验。notes给出的硬性要求:所有消费消息的业务代码,第一行必须是幂等判断。例如,订单消息的消费逻辑:
// 1. 根据消息中的order_id和biz_id(业务唯一标识)查询DB Order existingOrder = orderMapper.selectByBizId(message.getBizId()); if (existingOrder != null) { // 已存在,直接返回,不处理 return; } // 2. 创建新订单 createOrder(message);这里biz_id是业务生成的唯一ID(如UUID),与订单号无关,确保即使消息重发,也能识别出是同一笔业务。notes还强调:“不要依赖消息队列的‘Exactly Once’语义,它在分布式环境下极难保证。把幂等性做到应用层,才是唯一可靠的方案。”
4. 从“面试”到“生产”:那些笔记里没写,但决定成败的灰色地带
系统设计笔记最容易被忽略的,是那些无法画进架构图、不会出现在面试题里的“灰色地带”。它们不构成技术栈,却决定了系统是稳定运行还是三天两头告警。这份notes的终极价值,恰恰藏在这些“非技术细节”里——它们是我在生产环境里,用一次次故障换来的认知。
4.1 监控不是锦上添花,而是设计决策的“照妖镜”
一个经典误区:先设计系统,等上线后再补监控。结果往往是“监控覆盖不全,故障时找不到根因”。这份notes强制要求:监控指标必须在设计阶段就定义,并作为验收标准的一部分。例如,设计一个文件上传服务,笔记里会明确列出必须监控的5个黄金指标:1)上传成功率(HTTP 2xx/4xx/5xx比例);2)平均上传耗时(P50/P95/P99);3)单文件大小分布(识别异常大文件);4)存储后端写入延迟(区分上传耗时和存储耗时);5)存储空间使用率(预警阈值设为85%)。这些指标不是随便列的,而是对应着5类典型故障:成功率骤降指向网关或鉴权问题;P99耗时飙升指向存储后端瓶颈;大文件突增可能是爬虫或恶意上传;存储写入延迟高说明磁盘或网络问题;空间不足则预示着清理策略失效。我吃过亏:早期没监控“单文件大小”,结果一个用户上传了2GB的视频,占满单节点磁盘,导致整个集群上传失败。现在,所有新服务的设计文档里,“监控方案”章节和“架构图”章节一样重要,且必须由SRE和开发共同签字确认。
4.2 日志不是为了“看”,而是为了“重建现场”
日志设计常被当作开发收尾工作,但它是故障排查的生命线。这份notes里,日志规范比代码规范更严格。核心原则只有一条:“任何一行日志,必须能独立还原出完整的上下文”。这意味着:1)必须包含唯一trace_id(全链路追踪ID);2)必须包含关键业务标识(如order_id、user_id);3)必须包含明确的操作意图(如“开始处理支付回调”、“校验签名失败”);4)必须包含结构化字段(JSON格式),而非纯文本。我们曾因日志缺少trace_id,花费6小时排查一个跨服务调用失败问题——因为无法串联起调用链。现在,notes规定:所有日志框架(Logback/Log4j)必须预置MDC(Mapped Diagnostic Context),在请求入口处注入trace_id和业务ID,并在所有日志输出中自动携带。更关键的是,notes要求:“日志级别必须与业务重要性匹配”。例如,支付成功必须是INFO级别并包含完整订单信息;而“用户点击按钮”这类前端埋点日志,必须是DEBUG级别且采样率设为1%,避免日志爆炸。这看似是细节,却是能否在海量日志中快速定位问题的分水岭。
4.3 配置管理:不是写死的字符串,而是系统的“软肋”
配置常被当作“不重要”的部分,但它是系统最脆弱的环节。一份配置错误,可能让整个集群雪崩。这份notes把配置管理提升到架构设计高度,并列出三大死穴:1)配置热更新:所有影响运行时行为的配置(如限流阈值、超时时间、开关)必须支持不重启生效。我们曾因修改一个Redis连接池大小,不得不滚动重启所有服务,导致服务中断15分钟。现在,notes要求:所有核心配置必须接入Apollo或Nacos,且提供管理后台实时修改;2)配置灰度发布:新配置上线必须先在1%流量上验证。笔记里详细记录了如何用Spring Cloud Gateway的Predicate组合实现“按Header灰度”;3)配置变更审计:谁在什么时候修改了哪个配置?修改前后的值是什么?必须有完整审计日志。我们曾遭遇一次神秘的性能下降,最终发现是运维同事误将数据库连接池最大连接数从100改成10——没有审计日志,根本无法追溯。现在,notes规定:所有配置中心必须开启操作审计,并与企业微信机器人联动,每次变更实时推送告警。
4.4 容灾演练:不是“演”,而是“练”出肌肉记忆
最后,也是最常被忽视的一点:容灾方案必须定期演练,且演练必须真实。很多团队的容灾文档写得天花乱坠,但从未真正执行过。这份notes里,容灾演练不是“计划”,而是“季度强制任务”。规则很残酷:1)演练必须关闭真实服务(如停掉主库、断开某机房网络);2)必须由一线开发和SRE共同参与,而非仅由运维操作;3)必须记录从故障发生到业务恢复的全程时间,并分析每个环节耗时。我们第一次演练“数据库主库宕机”,从发现告警到切换完成花了47分钟,远超SLA承诺的5分钟。复盘发现:70%时间花在“确认是否真宕机”和“找负责人审批”上。于是notes新增两条铁律:1)所有关键服务必须配置自动故障检测和切换脚本(如MHA for MySQL),人工干预仅用于最终确认;2)建立“战时通讯群”,演练开始即全员禁言,只允许发送标准化指令(如“/switch-db primary-down”)。现在,同样的演练,平均恢复时间已压缩到3分12秒。笔记里写着:“容灾不是为了证明方案可行,而是为了暴露方案不可行的地方。每一次演练的失败,都是下一次生产的成功。”
5. 从“个人笔记”到“团队契约”:让设计能力沉淀为组织资产
一份高质量的system-design-notes,最终价值不在于作者个人,而在于它能否成为团队的“设计契约”。它把模糊的“经验”转化为清晰的“规则”,把依赖个人英雄主义的救火,变成可预测、可复制的工程实践。这份notes的终极形态,不是一份静态文档,而是一个活的、迭代的、嵌入研发流程的系统。
5.1 设计评审Checklist:把笔记变成会议议程
我们把notes里的核心验证点,提炼成一份15项的《系统设计评审Checklist》。它不再是评审会上的“自由讨论”,而是逐项打钩的强制流程。例如,评审一个新服务时,主持人必须按顺序提问:1)“你的QPS预估依据是什么?是否做过压测?”;2)“数据库分片键是什么?是否会导致热点?”;3)“缓存穿透防护方案是什么?是否验证过?”;4)“消息消费是否幂等?请展示代码”。如果任意一项无法当场回答,评审即终止,设计者需补充材料后重新申请。这个Checklist最大的作用,是把“我觉得有问题”变成“你没满足第7条”。它消除了主观判断,让评审聚焦在可验证的事实。我亲眼见证,推行Checklist后,团队设计返工率下降65%,线上重大故障中因设计缺陷导致的比例从42%降至9%。
5.2 架构决策记录(ADR):为每个选择留下“判决书”
笔记里最珍贵的,不是“做了什么”,而是“为什么这么做”。为此,我们强制推行ADR(Architecture Decision Record)制度。每个重大设计决策(如“选择Kafka而非RabbitMQ”、“采用分库分表而非读写分离”),都必须撰写一份ADR文档,包含:1)决策背景(要解决什么问题);2)备选方案(至少列出3个);3)评估标准(性能、成本、团队熟悉度等);4)最终选择及理由(量化对比);5)后续验证计划。这份文档不是存档,而是嵌入Git仓库,与代码共存。当新人接手项目时,第一件事不是看代码,而是读ADR——它告诉他,为什么这个服务要用gRPC而不是REST,为什么数据库索引要这样建。ADR让设计不再是个体的直觉,而成为团队共享的认知资产。我们曾因一个老服务的ADR缺失,导致重构时误判了技术债,多花了3周时间。现在,所有ADR都要求:必须由至少两名资深工程师联署,且每季度回顾一次,评估当初的决策是否依然成立。
5.3 技术雷达:让笔记成为团队的技术罗盘
最后,这份notes进化成了我们的“技术雷达”。它不再只是文字,而是一个动态的、可视化的技术选型指南。雷达分为四个象限:1)Adopt(采用):团队已验证、推荐在新项目中使用的方案(如Spring Cloud Alibaba、Prometheus);2)Trial(试用):已在小范围验证、鼓励尝试的方案(如eBPF网络监控);3)Assess(评估):值得关注、但尚未验证的新技术(如WebAssembly边缘计算);4)Hold(暂缓):因成熟度或生态问题,暂时不推荐的方案(如某些小众NoSQL)。每个象限的条目,都链接到对应的notes页面,包含详细评估报告、落地案例和避坑指南。技术雷达每季度更新,由架构委员会投票决定。它让技术选型从“个人喜好”变成“集体共识”,让新人能快速找到“团队认可的最佳实践”,也让技术决策有了可追溯的依据。当一个工程师提议引入新技术时,第一句话不再是“我觉得这个好”,而是“请看技术雷达的Assess象限,这里有我们的初步评估”。
我始终相信,系统设计能力不是天赋,而是习惯。它藏在每一次对“为什么”的追问里,藏在每一次对“验证”的坚持里,藏在每一次对“灰度”的敬畏里。这份notes,就是我用十年时间,把那些散落在故障报告、深夜复盘、模拟面试中的碎片,一块一块拼起来的镜子。它照见的不是完美的答案,而是我们如何一步步,把混沌的需求,变成可运行、可维护、可演进的系统。如果你正在这条路上,愿这份notes,成为你口袋里那把随时可用的螺丝刀——不大,但够紧每一颗关键的螺丝。