
1. 这不是笔记是系统设计能力的实体化切片“system-design-notes”这个标题乍看像一份随手记下的草稿但在我带过27轮校招面试、陪跑过43个中型后端团队做架构演进、亲手推翻重写过6套高并发业务系统的经验里它代表的是一类被严重低估的工程认知压缩包——不是知识罗列而是把分布式系统里那些抽象到让人头皮发麻的概念比如一致性哈希怎么在真实流量下不抖动、限流器如何在毫秒级波动中守住水位线用可触摸、可调试、可复盘的最小单元具象出来。核心关键词“system-design”和“notes”组合在一起本质是在说把系统设计从会议室白板上的箭头连线变成工程师每天能打开、能改、能测、能上线的代码片段与配置快照。它服务的对象非常明确正在准备System Design Interview的候选人需要快速建立技术直觉刚接手核心链路重构的中级工程师需要避开前人踩过的坑还有那些被“高可用”“弹性伸缩”等术语绕晕、却要马上给客户出方案的售前架构师。我见过太多人花三个月啃《Designing Data-Intensive Applications》结果第一次画订单系统架构图时连库存扣减该放DB还是Redis都犹豫十分钟——问题不在书读得少而在缺少把理论压进肌肉记忆的“notes”。这些笔记不是抄录是把CAP定理翻译成Nginx配置里的proxy_next_upstream参数把一致性哈希映射成Java里HashRing类里那行nodeList.get(Math.abs(hashCode % nodeList.size()))的实际调用栈。它解决的痛点极其具体当面试官问“如果秒杀库存超卖你怎么设计”你脑子里跳出来的不该是教科书定义而该是自己笔记里那个用Redis Lua脚本本地缓存双校验的流程图以及旁边手写的注释“实测在5万QPS下Lua执行耗时稳定在0.8ms但跨机房延迟波动导致超时率从0.02%升至0.3%最终加了本地布隆过滤器预判”。2. 笔记结构设计为什么必须按“问题域”而非“技术栈”组织2.1 拒绝“技术名词堆砌”坚持“场景驱动”的笔记骨架市面上90%的system-design笔记失败在第一步按技术分类。比如开篇就是“Redis篇”“Kafka篇”“Consistent Hashing篇”这等于把活生生的系统切成标本装进玻璃罐。真实世界里你永远不会因为“今天学了Kafka”就去设计一个消息系统——你是因为“订单状态变更要实时同步给物流、风控、BI三个下游且不能丢消息”才被迫研究Kafka。所以我的笔记目录永远以问题域为根节点/rate-limiter/、/cache-invalidation/、/id-generation/、/distributed-lock/。每个目录下不是罗列API而是三件套场景描述.md谁在什么压力下遇到了什么具体故障、方案对比.xlsx用真实压测数据说话比如令牌桶vs漏桶在突发流量下的P99延迟差异、可运行代码/配置/带Docker Compose的最小可验证环境。举个例子在/rate-limiter/目录里scene.md会写“2023年双11期间营销活动页接口因未做前置限流被爬虫刷到12万QPSDB连接池耗尽导致支付链路雪崩。事后复盘发现原方案仅在Nginx层用limit_req但未考虑用户登录态校验逻辑在应用层耗时波动实际请求穿透率高达37%”。这种写法强迫你把技术决策锚定在血淋淋的线上事故上而不是抽象概念。2.2 “notes”二字的真正含义记录决策背后的“不可见成本”很多人把notes理解成“抄重点”但真正的notes必须包含决策的暗面——那些不会写在架构图上却决定方案生死的细节。比如一致性哈希教科书只讲“虚拟节点解决数据倾斜”但我的笔记里会专门建/consistent-hashing/cost-analysis/子目录记录三组实测数据内存成本当节点数从100扩到1000虚拟节点数从1600涨到16000HashMap内存占用从2.1MB飙升至28MB触发JVM GC频率增加3倍计算成本用MurmurHash3 vs Java自带hashCode单次哈希计算耗时从12ns降到3.4ns但在10万QPS下这点差异让CPU使用率下降1.8%运维成本当某台机器宕机需下线传统一致性哈希要求客户端重新加载节点列表而我们用ZooKeeper监听节点变更实测配置推送延迟中位数42ms但P99达1.2s导致部分请求路由错误。这些数据不是凭空编造。我在/consistent-hashing/implementation/里放了完整的压测脚本用JMH跑哈希算法和ZK监听日志分析工具Python写的log parser。笔记的价值正在于把“理论上可行”和“实际上稳”之间的鸿沟用数字填平。你翻到任何一篇notes都能立刻判断“这个方案在我们集群规模下内存是否扛得住GC会不会成为瓶颈运维同学半夜接到告警时能不能5分钟内定位到是哈希环变更还是网络抖动”2.3 为什么“System Design Interview”是最佳训练场面试场景天然具备极端苛刻的约束条件45分钟内没有现成代码、没有历史数据、没有运维支持全靠一张嘴和一支笔构建系统。这恰恰逼出了最精炼的设计思维。我的笔记里所有方案都经过“面试压力测试”必须能在白板上3分钟画清核心数据流比如限流器我只画三层接入层Nginx限流、网关层Spring Cloud Gateway令牌桶、服务层Sentinel熔断每层标注关键参数Nginx的burst100、Gateway的replenishRate100、Sentinel的warmUpPeriodSec60绝不画无关的监控链路必须回答“如果XX组件挂了怎么办”在/rate-limiter/failover/里我写了三种降级策略的切换开关设计——当Redis集群不可用时自动切到本地Guava RateLimiter并记录降级日志当本地内存不足时再切到最简陋的计数器限流牺牲精度保可用必须给出可量化的取舍依据比如选Kafka还是RocketMQ做订单消息队列笔记里表格直接对比“Kafka吞吐量高37%但运维复杂度高2.1倍需维护ZKBrokerController三套组件而我们团队只有2个中间件工程师故选RocketMQ”。这种训练让笔记不再是纸上谈兵。去年有个学员用我的/id-generation/笔记准备面试面试官问“雪花算法时间回拨怎么处理”他没背标准答案而是掏出手机展示自己笔记里那段模拟时钟回拨的JUnit测试故意把系统时间倒拨5ms验证ID生成器是否抛出ClockMovedBackwardsException并自动等待面试官当场结束提问——因为这证明他真的把设计变成了可验证的代码。3. 核心模块深度拆解从rate limiter到consistent hashing的实战细节3.1 rate limiter为什么99%的实现死在“突发流量”上限流器常被当成“加个开关”的简单功能但真实战场远比想象残酷。我笔记里/rate-limiter/burst-handling/章节用2022年某电商大促的真实数据说话活动开始前10秒流量从5000QPS瞬间冲到8万QPS峰值持续17秒。当时线上用的Spring Cloud Gateway默认令牌桶结果出现两个致命问题令牌桶填充机制失灵Gateway的ReactiveRateLimiter默认每秒填充令牌但突发流量下填充线程被IO阻塞实际填充速率只有理论值的31%请求排队策略反向放大延迟当令牌耗尽请求进入等待队列但队列长度无上限导致P99延迟从12ms飙到2.3s大量请求超时。解决方案不是换框架而是分层限流动态参数接入层硬限流Nginx用limit_req zoneburst burst500 nodelayburst500是关键——它允许500个请求瞬时排队但nodelay确保它们立即被处理不等待令牌相当于用内存换响应速度网关层软限流Gateway关闭默认令牌桶改用Resilience4j的RateLimiter配置limitForPeriod10000每10秒1万个令牌limitRefreshPeriod10000刷新周期10秒并设置timeoutDuration100ms排队超时直接拒绝服务层精准限流Sentinel对库存扣减接口单独配置QPS5000并开启Warm Up模式预热60秒避免冷启动时流量冲击。提示Nginx的burst值不是拍脑袋定的。我笔记里附了计算公式burst (峰值QPS - 基线QPS) × 预估单次请求处理时间秒。比如基线5000QPS峰值8万QPS平均处理时间150ms则burst ≈ (80000-5000) × 0.15 11250。但实际设为500因为Nginx内存有限过大的burst会吃光worker进程内存。这是典型的“理论值”和“工程值”的差距。3.2 consistent hashing虚拟节点不是银弹必须配“动态权重”一致性哈希常被神化为解决数据倾斜的终极方案但我在/consistent-hashing/virtual-node-limitation/里用生产事故打了脸某社交App用户关系链路用160个虚拟节点做哈希环上线后发现3台机器负载相差4倍。查日志发现虚拟节点只是把物理节点“打散”但没解决节点能力差异——新采购的机器CPU是旧机器的2.3倍内存带宽高1.8倍而哈希环把请求均匀分配等于让强机器干弱机器1/2.3的工作量。解决方案是引入动态权重因子在哈希环初始化时不给每个物理节点分配固定数量虚拟节点而是按weight (CPU核数 × 内存GB × 网络带宽Gbps) / 基准值计算权重节点A权重1.0基准节点B权重2.3则节点B获得2.3倍的虚拟节点数比如节点A分100个节点B分230个更关键的是权重不是静态的。我笔记里写了用Prometheus采集node_cpu_seconds_total、node_memory_MemAvailable_bytes每5分钟计算一次实时权重通过gRPC推送到所有客户端。实测效果负载标准差从3.2降到0.4但代价是哈希环重建频率提高——每次权重调整都要重新计算所有虚拟节点位置。为此我设计了渐进式环更新新环生效时先将5%流量切过去10分钟后升到20%30分钟后全量。这需要客户端支持双环并存笔记里提供了Java版DualHashRing实现核心逻辑是public Node getNode(String key) { // 先查新环命中则返回 Node newNode newRing.getNode(key); if (newNode ! null Math.random() trafficRatio) { return newNode; } // 未命中或未到切流比例查旧环 return oldRing.getNode(key); }3.3 notes的“可执行性”为什么必须带Docker Compose和压测脚本真正的notes必须能一键跑起来。我在每个模块目录下都放docker-compose.yml和load-test.sh。以/rate-limiter/为例docker-compose.yml启动3个服务nginx带限流配置、gatewaySpring Boot Resilience4j、backend模拟库存服务load-test.sh用wrk发起阶梯式压测“先5000QPS持续30秒再1万QPS持续20秒最后8万QPS冲击10秒”并自动抓取各层指标Nginx的limit_req_status、Gateway的resilience4j.ratelimiter.available.permissions、Backend的jvm_memory_used_bytes压测结果生成report.md包含关键图表Nginx拒绝率曲线、Gateway排队时长分布、Backend GC次数。注意压测脚本必须模拟真实业务特征。比如电商秒杀不能只压URL要带X-User-ID头触发一致性哈希要随机item_id避免缓存击穿还要在请求体里加{version: 20231025}测试灰度发布。我笔记里/load-test/realistic-scenario/详细列了12种业务特征模拟方法比如用jq生成随机JSON、用awk按概率注入错误参数。4. 实操过程从零搭建你的system-design-notes仓库4.1 目录结构与文件命名规范让笔记“自我解释”一个混乱的目录结构会让笔记迅速沦为垃圾场。我的规范强制所有文件名带版本号和场景标识system-design-notes/ ├── /rate-limiter/ │ ├── v1.2-burst-handling-scene.md # 场景描述v1.2表示第2次修订 │ ├── v1.2-burst-handling-comparison.xlsx # 方案对比表 │ ├── v1.2-burst-handling-docker/ # 可运行环境 │ │ ├── docker-compose.yml │ │ └── nginx/conf.d/limit.conf │ ├── v1.2-burst-handling-loadtest/ # 压测脚本 │ │ ├── load-test.sh │ │ └── report-template.md │ └── v1.2-burst-handling-lessons.md # 教训总结如“Nginx burst值超2000导致OOM” ├── /consistent-hashing/ │ ├── v2.1-dynamic-weight-scene.md │ └── ... └── README.md # 仓库总览含贡献指南关键原则版本号不是随意加的每次线上故障复盘、压测数据更新、方案迭代都必须升小版本v1.1→v1.2大版本v1.x→v2.0只在架构级变更时用如从Redis限流迁移到Service Mesh限流文件名必须暴露上下文burst-handling-scene.md比design.md有用一万倍看到文件名就知道这篇讲什么、在哪用每个目录必须有lessons.md这是笔记的灵魂。里面不写正确答案只写“我们错在哪”。比如/rate-limiter/lessons.md第一条“2023-08-12未配置Nginxburst参数导致大促首屏加载失败率12%。根本原因误以为Gateway层限流足够忽略了接入层到网关的网络延迟放大效应。”4.2 工具链选择为什么用MarkdownExcelDocker不用Notion或Confluence很多人用Notion做笔记但Notion无法承载“可执行性”。我的选择基于三个硬需求版本可追溯Git能精确追踪v1.2-burst-handling-scene.md哪一行被谁在何时修改Notion的版本历史是模糊的时间点环境可复现Docker Compose文件能保证任何人git clone docker-compose up就得到一模一样的测试环境Notion里贴的截图永远是“过去时”数据可计算Excel表格里的压测数据能直接用公式算出P99延迟提升百分比Notion的数据库字段无法做复杂计算。具体工具链写作VS Code Markdown All in One插件支持数学公式、表格自动生成绘图PlantUML文本生成序列图/部署图代码写在.puml文件里和笔记一起Git管理压测wrkHTTP k6WebSocket 自研Java压测框架用于复杂业务链路监控Prometheus Grafana所有仪表盘配置导出为JSON存入/monitoring/目录。实操心得PlantUML的序列图必须带activate和deactivate标注生命线。比如画限流流程Nginx激活后Gateway才激活Backend最后激活——这能直观暴露“谁在等谁”避免画出理想化的并行图。我笔记里所有图都遵循此规范因为真实系统里90%的性能问题源于意外的串行等待。4.3 如何让笔记“活”起来每日15分钟的维护仪式笔记不是写完就扔的文档而是持续进化的系统。我给自己定的铁律每天早会后15分钟扫一眼当天线上告警如果涉及笔记里的模块比如/cache-invalidation/告警立刻更新lessons.md写清现象、根因、修复动作每周五下午运行./run-all-tests.sh遍历所有/xxx/loadtest/目录执行压测脚本生成weekly-report.md对比上周数据标记性能退化项每月1号用git log --sincelast month --oneline生成变更摘要发到团队群标题是“本月笔记进化3处优化让库存扣减P99降低22ms”。这个仪式感让笔记从“个人知识库”变成“团队技术资产”。去年我们团队用这套笔记支撑了6次大促0次因架构问题导致故障运维同学反馈“现在看告警第一反应不是翻文档而是直接cd system-design-notes grep cache-invalidation5分钟内找到对应方案。”5. 常见问题与排查技巧实录那些教科书不会写的坑5.1 “为什么我的一致性哈希在测试环境完美上线就倾斜”这是最高频问题。表面看是哈希算法问题实则90%是键空间分布不均导致。比如用户ID做哈希键测试用user_001到user_1000但线上真实ID是user_1000000001到user_1000001000高位数字相同导致哈希值聚集。排查步骤抓取线上真实键样本在应用层加日志采样1%请求的key输出到/tmp/keys.log用Python分析分布# analyze_keys.py from collections import Counter import hashlib keys [line.strip() for line in open(/tmp/keys.log)] hashes [int(hashlib.md5(k.encode()).hexdigest()[:8], 16) % 1000 for k in keys] print(Counter([h//100 for h in hashes])) # 按百位分桶统计结果解读如果输出{0: 420, 1: 12, 2: 8, ...}说明95%的key哈希到0-100区间这就是倾斜根源。解决方案加盐Saltkey salt再哈希salt用服务实例ID或时间戳二次哈希对原始key做SHA256取后8位再哈希业务键改造把user_1000000001改成1000000001_user打破数字前缀规律。注意加盐方案必须全局一致。我笔记里/consistent-hashing/salt-strategy/明确写了“salt必须由配置中心下发禁止硬编码”因为曾有团队在不同机器上用了不同salt导致同一key路由到不同节点。5.2 “限流器配置明明正确为什么P99延迟还是飙升”限流器本身没问题问题在限流决策点与业务逻辑耦合太深。典型场景在Spring Controller里先做限流检查再查DB库存结果限流通过后DB查询慢整体延迟高。根因分析表环节表面行为真实瓶颈解决方案Nginx层limit_req拒绝率0%Nginx到Gateway网络延迟波动加proxy_next_upstream error timeout重试Gateway层available.permissions充足Gateway解析JWT耗时不稳定JWT解析移至Nginx用lua-resty-jwtService层Sentinel QPS达标库存扣减SQL未走索引在/rate-limiter/dependency-check/里加SQL执行计划检查脚本关键技巧把限流当作“前置守门员”而不是“最后一道防线”。我在笔记里强制所有接口的限流点必须在“最轻量级操作之后”Nginx后是Header解析Gateway后是JWT校验Service后才是DB访问。这样即使DB慢限流器也能在更早阶段拦截。5.3 “笔记写了很多但面试时还是答不好为什么”根本原因是笔记没经过“口语化转译”。书面笔记是给机器看的可执行、可验证面试回答是给人听的有逻辑、有画面。我的补救方案每篇notes配interview-answer.md用STAR法则重写。比如/rate-limiter/的STARSituation2022年双11营销页QPS从2000突增至5万Task48小时内设计限流方案保证支付链路P99200msAction采用Nginx硬限流burst500 Gateway软限流timeout100ms Service层熔断错误率5%自动降级Result大促峰值QPS 8万支付链路P99稳定在182ms0超时。录音自测用手机录下自己讲interview-answer.md回放时删掉所有“然后”“啊”“这个”等填充词确保45秒内说完核心逻辑。实操心得面试时别背笔记要“讲事故”。当面试官问“怎么设计秒杀”直接说“去年我们遇到过类似问题当时库存扣减接口在1万QPS下P99飙到1.2秒后来发现是Redis Pipeline没用好...”——真实事故比理论更有说服力。6. 最后一点体会笔记的终点是“不再需要笔记”写system-design-notes的终极目的不是攒一堆文档而是让那些曾经让你彻夜难眠的架构难题变成肌肉记忆里的条件反射。我记得第一次独立设计限流方案时对着Nginx文档查了3小时limit_req参数手心全是汗现在看到流量曲线陡升手指已经自动敲出burst500 nodelay。这种转变不是靠背诵而是靠把每一个“为什么”钉进真实世界的坐标里——那个凌晨三点修复的缓存雪崩那个被一致性哈希坑惨的上线夜那个在面试白板上画歪了三次的数据流图。notes只是路标真正的路是你一次次把理论砸进生产环境的裂缝里听着它碎裂又重组的声音。所以别追求“完美笔记”先写下来哪怕只有三行别等“完全懂了”再动手先跑通一个Docker Compose更别怕写错我最早的/rate-limiter/lessons.md第一条就是“v0.1误把burst设为10000导致Nginx OOM重启损失5分钟交易”。正是这些带着挫败感的记录让笔记有了温度也让系统设计这件事从遥不可及的神坛落回工程师指尖可触的键盘上。