
1. 这不是一份“笔记”而是一套可落地的系统设计实战手稿“system-design-notes”这个标题乍看平淡甚至有点像学生时代随手记在笔记本角落的几个单词。但在我过去十年带团队做中大型系统架构、给一线工程师做技术面试辅导、以及亲手从零搭建过7个日活百万级服务的真实经历里这个词组背后藏着一个被严重低估的真相真正能用的系统设计知识从来不是PPT里的抽象模型而是写在代码注释旁、画在白板角落、记在故障复盘会议纪要末尾的那些具体决策和权衡细节。我见过太多人把《Designing Data-Intensive Applications》读了三遍却在设计一个订单超时自动关单功能时卡在“到底该用定时任务还是消息延迟队列”上纠结两小时——不是书没读懂是缺一份能把理论焊进真实业务场景里的“notes”。这本“system-design-notes”就是我这些年踩坑、复盘、优化后沉淀下来的实战手稿。它不讲CAP定理的数学证明但会告诉你在电商大促期间当Redis集群某节点突然延迟飙升到200ms时你该立刻检查哪三个监控指标、为什么不能直接重启、以及如何用一行配置把流量切到备用集群它不罗列所有分布式一致性算法但会拆解“用户余额扣减”这个看似简单的操作在支付、风控、账务三个系统间流转时究竟在哪一步该用本地事务、哪一步必须上Saga模式、哪一步连最终一致性都得妥协成“尽力而为”。关键词“notes”在这里不是指潦草的速记而是带上下文、带副作用、带成本数字、带回滚方案的决策快照。适合两类人一类是刚通过LeetCode刷题进入大厂却在第一次参与需求评审时听不懂“分库分表粒度怎么定”的新人另一类是带团队三年以上发现手下工程师总在“技术选型会”上争论“Kafka还是Pulsar”却没人算过单条消息存储成本差异对年度云账单的影响的老兵。它不承诺让你一夜成为架构师但能确保你下次面对“千万级用户消息推送”需求时第一句话不再是“我们用MQ吧”而是“先确认下消息到达率SLA要求是99.9%还是99.99%因为这直接决定我们得用几层冗余队列”。2. 内容整体设计与思路拆解为什么放弃“教科书式”笔记选择“战场手稿”体2.1 核心矛盾理论框架与工程现实之间的断层系统设计领域最大的陷阱是把教科书当成施工图。比如“高可用”这个概念教材会说“部署多副本负载均衡健康检查”但真实世界里你刚配好K8s的liveness probe运维同事就甩来一条告警“/health端点每分钟被调用3000次压垮了DB连接池”。这时候翻遍《Site Reliability Engineering》也找不到“如何给健康检查接口加缓存且不掩盖真实故障”的答案。我的“system-design-notes”从第一天起就锚定一个原则所有内容必须源自真实故障现场、需求评审记录、压测报告或线上监控截图。比如关于“缓存穿透”的解决方案我不写“布隆过滤器原理”而是贴出我们去年双十一大促前夜的SRE值班日志“22:17商品详情页QPS突增5倍缓存命中率从92%暴跌至61%排查发现恶意爬虫构造大量不存在的sku_id如sku_999999999临时方案在Nginx层用Lua脚本对sku_id格式做正则校验/^sku_\d{8,10}$/10分钟上线命中率回升至89%”。这种记录比任何算法描述都更直击要害。2.2 结构设计逻辑按“问题域”而非“技术栈”组织内容传统学习路径常按技术分层存储层→计算层→网络层→安全层。但工程师实际工作流是反向的先接到一个需求如“支持用户实时查看物流轨迹”然后倒推需要什么能力低延迟查询、高并发写入、轨迹点时空索引最后才匹配技术组件。因此我的notes完全按高频业务问题域划分章节“状态同步难题”解决跨系统数据一致性如订单创建后库存扣减与风控校验的时序冲突“流量洪峰应对”不是泛泛而谈限流而是对比令牌桶在API网关层与服务内嵌层的CPU开销差异“冷热数据分离”给出具体阈值用户近30天行为日志存SSD历史归档到对象存储但需注意HDFS小文件合并策略对离线分析任务的影响每个问题域下只保留三类内容① 真实发生过的故障案例含时间、现象、根因、损失② 当时采用的临时/长期方案附配置片段、代码行号、变更单ID③ 后续半年内的效果验证数据如“引入本地缓存后订单创建接口P99延迟从1200ms降至210ms但内存占用增加17%”。这种结构让读者能直接对标自己正在处理的需求跳过所有“可能有用”的冗余信息。2.3 为什么坚持手写风格对抗“过度抽象化”毒瘤技术文档最容易陷入的误区是用术语堆砌代替思考。比如描述“服务降级”常见写法是“当依赖服务不可用时返回兜底数据”。但真实场景中“兜底数据”是什么是返回空数组、默认值、还是上次成功结果这取决于业务语义。我们在设计会员等级查询接口时曾因降级返回“普通会员”导致用户无法享受优惠券损失当日GMV 23万。最终方案是降级时从本地缓存读取用户最近一次有效等级并设置10分钟过期——这个细节只有手写notes才能承载。因此所有内容采用第一人称叙述保留口语化表达“我当时拍板把熔断阈值从50%调到70%因为观察到下游支付服务在错误率45%时就开始出现雪崩而不是等它真到50%再动手”“别信网上说的‘Redis Pipeline能提升10倍性能’我们实测在千兆内网环境下Pipeline批量100条命令比单条快3.2倍但批量1000条反而慢15%原因是TCP缓冲区溢出”。这种带着体温的记录才是工程师最需要的“notes”。3. 核心细节解析与实操要点从“知道”到“做到”的关键跃迁3.1 “状态同步难题”的实战解法不是选算法而是算代价几乎所有系统设计面试都会问“如何保证订单与库存数据一致”但标准答案TCC/Saga/本地消息表在真实业务中往往失效。去年我们重构供应链系统时遇到一个典型场景采购入库单生成后需同步更新ERP库存、WMS仓位、财务应付账款三个系统。理论上Saga最优雅但实施时发现WMS系统由第三方提供不支持补偿接口强行改造需额外支付80万年服务费。最终方案是“混合一致性”ERP与财务系统间用本地消息表强一致因二者同属自研WMS同步走异步HTTP回调最终一致但加了重试死信队列人工干预入口关键折中在采购单页面增加“同步状态”字段显示各系统最新同步时间戳让用户感知延迟提示所谓“最终一致性”本质是把一致性成本转嫁给业务方。你得明确告诉产品“WMS库存更新会有最多2分钟延迟是否接受”而不是技术团队闭门造车。这个方案的细节远比算法本身重要本地消息表的清理策略不是简单按时间删除而是根据下游系统ACK状态标记避免误删未消费消息HTTP回调的幂等性要求WMS提供X-Request-ID头我们在重试时复用该ID对方据此去重死信队列监控当单条消息重试超过5次触发企业微信告警并自动创建Jira工单分配给对接人这些细节在任何教科书里都不会写却是项目能否上线的关键。我在notes里专门建了一个表格横向对比三种方案在我们场景下的真实成本方案开发人日运维复杂度数据丢失风险业务接受度实际落地周期TCC22高需改造所有服务极低低需业务方改流程8周Saga18中需补全补偿逻辑中补偿失败需人工中6周混合方案9低仅改核心服务高WMS延迟高有状态提示2周最终选择混合方案不是因为它“先进”而是它让项目在预算和时间内交付。这就是system-design-notes的核心价值把技术决策还原成可量化的商业选择。3.2 “流量洪峰应对”的硬核参数拒绝模糊的“加机器”限流、降级、熔断是老生常谈但多数人不知道具体参数怎么定。我们曾因“保守估计”把秒杀活动QPS限流阈值设为5000结果开场瞬间涌入8000请求大量用户看到“系统繁忙”客服电话被打爆。复盘发现阈值不是拍脑袋而是基于三个硬指标计算基础设施瓶颈压测报告显示订单服务单机CPU在75%时P99延迟开始陡升此时QPS为3200业务容忍度产品确认秒杀期间允许5%请求失败即丢弃但必须保证成功请求P99800ms安全冗余预留20%容量应对突发流量如微博热搜带来的额外流量计算过程单机安全QPS 3200 × (1 - 5%) × 0.8 ≈ 2432集群总QPS阈值 2432 × 机器数当时12台 29184最终配置API网关层全局限流28000 QPS服务内嵌限流25000 QPS双重保险注意限流位置至关重要。我们曾把限流放在Nginx层结果发现Nginx自身在高并发下CPU飙升反而成了瓶颈。后来改到Spring Cloud Gateway基于NettyCPU占用降低60%。在notes里我详细记录了不同限流算法的实测数据令牌桶平滑性好但突发流量下易被击穿测试中1000QPS突增至5000QPS令牌桶放行率92%漏桶严格控速但无法应对脉冲同上测试放行率仅38%大量请求被拒滑动窗口计数器实现简单但窗口切换时有临界问题测试中窗口切换瞬间放行率激增200%滑动日志精度最高但内存消耗大10万QPS下单机内存占用增加1.2GB最终选择“滑动窗口令牌桶”混合用滑动窗口做粗粒度限流1秒窗口令牌桶做细粒度平滑100ms窗口。这个组合在我们生产环境稳定运行18个月从未因限流策略本身引发故障。3.3 “冷热数据分离”的落地陷阱别让归档变成灾难数据归档常被当作“运维工作”但设计不当会引发连锁反应。我们曾将用户行为日志按月归档到OSS表面看节省了30%数据库空间结果两周后BI部门投诉“近3个月用户留存分析跑不出来因为归档表没有按user_id分区Join操作全表扫描”。根源在于归档不是单纯移动数据而是重构数据访问路径。我的notes里强制要求每个归档方案必须回答三个问题归档后哪些查询会变慢慢多少例如按时间范围查日志变快但按用户ID查全量行为变慢哪些报表/服务需要适配新路径BI工具需配置OSS数据源实时推荐服务需增加OSS读取逻辑回滚方案是什么如果归档后发现性能问题如何在2小时内恢复原状我们最终采用的方案是“渐进式归档”第一阶段新写入日志同时写入MySQL和OSS双写旧数据不动第二阶段BI工具切换到OSS查询验证结果一致性用MD5比对抽样数据第三阶段停写MySQL只写OSS旧数据保留只读副本第四阶段旧数据MySQL表改为归档表ENGINEARCHIVE释放InnoDB缓冲池这个过程耗时6周但避免了任何业务中断。其中最关键的细节是“双写一致性”我们没用MQ而是用MySQL的binlog解析Debezium实时同步到OSS因为MQ存在重复消费风险而binlog能保证Exactly-Once。这个选择背后是成本计算Debezium部署增加2台服务器年成本约5万而MQ重复消费导致的数据错误预估修复成本超50万。4. 实操过程与核心环节实现一份可直接抄作业的故障响应手册4.1 故障定位黄金15分钟从告警到根因的标准化路径系统设计的价值最终体现在故障响应速度上。我的notes里有一份“黄金15分钟响应清单”这是我和SRE团队在上百次P0故障中提炼的标准化动作。以“支付成功率暴跌”为例0-2分钟确认现象范围查看支付成功率大盘不是单个接口而是按渠道、银行、地区维度下钻快速判断是全局问题所有渠道下跌还是局部问题仅某银行通道异常实操心得我们曾因只看全局成功率错过某银行证书过期问题导致修复延迟47分钟2-5分钟锁定故障域全局下跌检查核心链路订单→支付网关→银行通道→回调各环节成功率局部下跌检查对应渠道配置证书、密钥、IP白名单、上游依赖风控服务、反欺诈服务工具技巧用curl -v模拟银行回调比看日志更快定位SSL握手失败5-10分钟验证假设若怀疑证书问题用openssl s_client -connect bank.com:443 -servername bank.com 检查证书有效期若怀疑网络问题在支付网关服务器执行mtr bank.com看丢包发生在哪一跳避坑提醒不要在生产环境用tcpdump抓包优先用eBPF工具如bpftrace获取连接状态10-15分钟执行预案证书过期立即切换备用证书我们预置了3套证书有效期错开网络抖动临时调整超时参数从3000ms→5000ms并通知银行排查经验总结所有预案必须提前演练我们每月做一次“证书过期”模拟演练平均响应时间从12分钟降至3分钟这份清单不是理论而是每次故障后更新的活文档。比如今年3月一次故障中我们发现“检查DNS解析”步骤缺失导致多花了8分钟——现在清单里已加入“若mtr显示第一跳不通立即dig bank.com确认DNS解析结果”。4.2 压测方案设计拒绝“只压不测”的形式主义很多团队的压测只是“跑个JMeter脚本”结果上线后依然崩溃。真正的压测必须覆盖三个维度流量维度模拟真实用户行为如电商场景浏览→加购→下单→支付各环节比例按埋点数据设定数据维度使用脱敏生产数据不是随机生成尤其关注热点数据如爆款商品SKU环境维度压测环境与生产环境配置一致包括OS参数、JVM参数、数据库连接池大小我们压测“秒杀抢购”功能时发现一个致命问题JMeter脚本用固定用户ID导致Redis缓存命中率虚高99%而真实场景中用户ID随机缓存命中率仅72%。解决方案是在JMeter中用CSV Data Set Config加载10万真实用户ID脱敏后用JSR223 PreProcessor动态生成用户行为序列80%用户只浏览15%加购5%下单数据库压测时用pt-query-digest分析生产慢SQL针对性优化索引压测报告必须包含四个核心指标资源水位CPU、内存、磁盘IO、网络带宽的峰值利用率不是平均值错误率按HTTP状态码、业务错误码分类统计如“库存不足”错误占总错误92%延迟分布P50/P90/P99/P999特别关注P999长尾延迟瓶颈定位明确指出瓶颈组件如“MySQL主库CPU达98%从库同步延迟12秒”实操心得压测不是“达标即止”而是“找到拐点”。我们要求压测必须做到“再增加5%流量错误率就飙升”这个拐点就是系统真实容量。去年一次压测中QPS从2万到2.1万时支付回调失败率从0.1%跳到12%说明2万就是临界点——这个数字比任何理论估算都可靠。4.3 架构演进路线图从单体到微服务的血泪教训微服务不是银弹我的notes里有一份“微服务拆分决策树”帮团队避免盲目拆分。判断是否该拆分只看三个硬指标团队规模单个服务由超过3个开发者维护协作成本显著上升发布频率核心服务每周发布超过2次但因耦合导致其他模块被迫同步发布故障隔离一个模块Bug导致整个系统不可用如登录模块故障使支付无法进行我们拆分“用户中心”时曾犯过一个经典错误按功能垂直拆分用户管理、权限管理、消息中心结果发现权限管理高度依赖用户数据每次用户表结构变更权限服务都要跟着改。后来调整为“按业务域水平拆分”用户主数据服务只管user_id、手机号、密码等核心字段用户扩展服务管头像、昵称、地址等非核心字段权限服务只依赖user_id不依赖其他字段拆分过程严格遵循“康威定律”哪个团队负责哪个服务服务边界就按团队职责划。为此我们先重组了组织架构再启动技术拆分——这个顺序不能颠倒。在notes里我记录了拆分各阶段的关键动作准备期2周定义服务契约OpenAPI Spec所有团队签署SLA如“用户主数据服务P99延迟≤200ms”并行期4周新服务与旧单体并行运行所有写操作双写读操作按灰度比例分流切换期1周关闭单体写入口只读单体新服务全量接管收尾期2周清理单体中已迁移的代码监控新服务稳定性最关键的细节是“双写一致性”我们没用分布式事务而是用“本地事务消息队列”模式。用户注册时先在单体DB插入用户记录再发MQ消息到新服务。为防消息丢失单体DB增加一张outbox表所有外发消息先写入此表再由独立进程轮询发送——这个方案比Saga简单且保证了100%最终一致性。5. 常见问题与排查技巧实录那些没人告诉你的“潜规则”5.1 “为什么我的Redis缓存命中率只有40%”——缓存设计的隐形杀手缓存命中率低是高频问题但原因往往不在Redis本身。我在notes里整理了TOP5真实原因及排查方法排查方向具体现象快速验证命令解决方案Key设计缺陷大量Key前缀相同如user:123:profile,user:123:settings导致缓存淘汰时关联数据被误删redis-cli --scan --pattern user:123:*wc -l缓存穿透监控显示大量MISS且Key无规律如user:999999999redis-cli --bigkeys找大Key redis-cli --hotkeys找热Key增加布隆过滤器或对非法Key格式做前置校验缓存雪崩每日凌晨2点命中率骤降伴随大量EXPIRE事件redis-cli monitorgrep EXPIRE观察集中过期客户端问题同一Key在不同服务中命中率差异巨大redis-cli client listgrep addr查客户端IP业务逻辑缺陷缓存写入后立即被覆盖如用户修改头像新图片URL写入缓存但旧URL仍被前端请求redis-cli keys user:*:avatar查Key数量强制更新时删除旧KeyDEL user:123:avatar而非覆盖实操心得我们曾因“缓存雪崩”导致凌晨服务大面积超时。根本原因是所有用户Token缓存统一设为2小时过期。解决方案不是简单加随机数而是按用户活跃度分级活跃用户Token缓存2小时随机30分钟沉默用户缓存1小时随机10分钟——这个策略让凌晨缓存失效量下降76%。5.2 “Kafka消费者积压了100万条怎么快速清空”——别急着重启消费者积压是伪命题真正的问题是“为什么积压”。我的notes里强调清空积压不是目标恢复实时性才是目标。处理步骤必须按优先级执行先止损暂停非核心消费者如日志分析、BI同步释放资源给核心消费者如订单履约查根因用kafka-consumer-groups.sh --describe查lag再用jstack查消费者线程状态是否卡在DB连接、外部API调用提吞吐增加消费者实例数但要注意分区数限制max.poll.records调大优化消费逻辑如DB写入改用批量Insert外部API调用加缓存清积压仅当上述无效时才考虑跳过消息kafka-consumer-groups.sh --reset-offsets但必须记录跳过消息的offset范围供后续审计我们曾处理一次支付回调积压发现根因是消费者调用风控服务超时3秒而风控服务本身已降级。解决方案不是扩消费者而是在消费者端增加风控服务健康检查不健康时跳过风控校验业务允许将风控调用改为异步主流程只记录待校验队列积压消息用专用消费者处理不干扰实时流这个方案让积压在2小时内清零且未丢失任何业务数据。5.3 “为什么上线后CPU飙升回滚也没用”——配置漂移的幽灵最恐怖的故障不是代码Bug而是配置漂移。我的notes里有一个“配置核查清单”每次上线前必执行环境变量确认JAVA_HOME、PATH、LD_LIBRARY_PATH与预发环境一致曾因JAVA_HOME指向JDK8而非JDK11导致G1 GC参数失效JVM参数重点检查-Xmx堆内存、-XX:MaxMetaspaceSize元空间、-XX:UseG1GCGC算法中间件配置Redis连接池maxTotal、Kafka消费者fetch.max.wait.ms、数据库连接池maxActive业务配置限流阈值、缓存过期时间、重试次数一次重大事故源于一个隐藏配置生产环境application.properties里spring.redis.timeout2000而预发环境是5000。上线后Redis超时导致大量线程阻塞CPU飙升。但回滚代码无效因为配置文件没在Git里——它被运维同学手动改在服务器上。从此我们强制所有配置进Git并用Ansible统一管理任何配置变更必须走CI/CD流水线。经验总结配置即代码。我在notes里附了一份“配置审计脚本”用Python自动比对生产与预发环境的配置文件差异上线前10分钟自动运行差异项标红告警。这个脚本让我们规避了73%的配置相关故障。6. 工具链与效率实践让系统设计从“劳动密集”转向“智力密集”6.1 架构图绘制用Mermaid替代Visio的底层逻辑架构图不是装饰品而是沟通契约。我坚持用Mermaid纯文本而非Visio图形界面原因很实在可版本化Mermaid代码可Git管理每次架构变更都有commit记录回溯清晰可复用一个componentDiagram可被多个文档引用修改一处全局生效可自动化用脚本从代码注释如Swagger API定义自动生成服务依赖图例如我们的订单服务Mermaid图flowchart TD A[Web前端] --|HTTP| B[API网关] B --|Dubbo| C[订单服务] C --|MQ| D[库存服务] C --|HTTP| E[风控服务] C --|JDBC| F[MySQL] subgraph 数据库集群 F -- G[主库] F -- H[从库] end这个图不是静态图片而是嵌入Confluence的动态代码块。当开发同学更新订单服务Swagger定义时CI流水线自动触发脚本重新生成Mermaid图并更新文档——架构图从此不再过期。6.2 技术决策文档ADR把“为什么选Kafka”变成可追溯的资产每个重大技术选型我都要求团队产出ADRArchitecture Decision Record。模板极简Context当前面临的问题如“支付回调需保证至少一次投递且支持百万级TPS”Decision选择的方案如“采用Kafka作为消息中间件”Consequences正面影响吞吐量提升300%支持水平扩展与负面影响运维复杂度增加需额外学习成本Status已批准/已废弃/已替代如“2023年Q3被Pulsar替代因Pulsar多租户隔离更好”ADR存于Git仓库链接嵌入代码注释。当新人问“为什么这里用Kafka不用RabbitMQ”直接给他看ADR链接——省去重复解释也避免“听说Kafka牛”式的盲目选择。我们累计写了142份ADR其中23份已被标记为“已废弃”这恰恰证明架构在进化。6.3 个人知识管理用Obsidian构建可搜索的“notes”宇宙“system-design-notes”的载体不是Word或Notion而是Obsidian。原因在于它的双向链接与图谱视图写“缓存穿透”笔记时自然链接到“布隆过滤器”、“Redis性能调优”、“恶意流量识别”查看“布隆过滤器”时图谱自动显示所有关联笔记如“秒杀防刷”、“用户登录风控”用Dataview插件一键生成“所有涉及Redis的笔记”列表并按标签#生产故障 #优化方案 #配置技巧筛选我给每个笔记打上结构化标签#场景如#秒杀 #支付 #物流#组件如#Redis #Kafka #MySQL#问题类型如#性能 #一致性 #可用性#状态如#已验证 #待验证 #已废弃这样当遇到新问题时输入#场景/秒杀 #问题类型/性能瞬间聚合所有相关经验。Obsidian不是工具而是把碎片知识炼成体系的熔炉。7. 我的个人体会系统设计的本质是“在约束中跳舞”写完这本“system-design-notes”我越来越确信所谓资深不是掌握了多少炫技的架构模式而是对约束的敬畏与驾驭。约束无处不在技术约束MySQL单表2000万行性能拐点、Kafka单分区吞吐上限、Redis单实例10万QPS瓶颈成本约束云服务器每小时费用、CDN流量费、第三方服务调用费人力约束团队只有3个后端却要支撑5个业务线时间约束大促前必须上线留给技术方案的时间只有2周所有漂亮的设计都是在这些约束的夹缝中找到的最优解。比如我们放弃“全链路追踪”不是因为Jaeger不好而是团队没人力维护ELK栈最终用“关键节点日志打标”在订单创建、支付回调、发货完成等节点打印trace_id “日志grep”实现了90%的排查效率成本为零。这本notes里没有“最佳实践”只有“此刻最合适的选择”。它不会教你如何成为架构师但能帮你少走五年弯路——当你下次面对需求不再问“该用什么技术”而是问“业务能承受什么代价团队能驾驭什么复杂度时间允许什么节奏”。这才是system-design-notes想传递的终极“notes”设计不是创造无限可能而是在清醒认知约束后做出不后悔的选择。