Skip to content

性能优化方法论 ​

定位:工程实践方法论(工程方法论系列收官篇之一)。性能优化不是技巧清单,是一条纪律链:先测量、再归因、按层下刀、验证固化。没有测量就没有优化,只有占卜。本篇与 观测性三件套(信号从哪来)、问题定位步骤(排查动作)互链。文末「速查附录」收纳早期速查版的四步要点。

第一定律:优化前先测量 ​

没有 profile 的优化是占卜。 测量分两层:观测三件套负责"选地方",剖析三件套负责"出证据"。

层回答手段输出形态
指标(面)慢在哪个接口/哪个资源Prometheus P99/错误率/饱和度时序曲线
链路(线)慢在哪一段 spanOTel / Arthas tracespan 耗时排序
剖析(点)慢在哪一行/哪个对象见下"剖析三件套"热点证据

剖析(profile)三件套——拿到了"面"和"线"之后的最后一公里:

剖析件抓什么典型工具典型产出
CPU 剖析计算热点(哪段代码烧 CPU)async-profiler 火焰图 / Arthas profiler最宽的塔=最大头的栈
内存剖析对象增长与持有jmap + MAT 支配树、GC 日志增量最大的对象及其 GC Roots 链
线程与锁剖析等待与阻塞(IO/锁/池排队)jstack(多次对比)、Arthas thread -bBLOCKED 堆积、锁持有者

三个纪律:基线先行(优化前的数字先记下来,否则"快了"无法证明)。对比采样(两份间隔抓取对比,单次快照会误导)。测量环境要代表真实(压测数据要有真实流量画像,否则优化的是"另一个系统")。

第四条纪律:优化前先约定靶心——"P99 从 3.2s 降到不超过 500ms 即达标"。没有靶心的优化会在"再快一点"里无限滑行,投入产出在不知不觉中劣化。

测量的收敛顺序是一个漏斗,从面到点,代价递增、结论递实:

text
 指标(面)      哪个接口慢?P99 3.2s,其他接口正常          ← 秒级,看大盘
   ↓
 链路(线)      哪一段慢?下单 → 库存查询 span 1.8s         ← 分钟级,拉一条 trace
   ↓
 剖析(点)      为什么慢?CPU/内存/锁三件套各跑一遍         ← 需要动实例,低峰执行
   ↓
 代码行          全表扫描 40 万行/次,缺联合索引              ← 证据闭合,才开始谈优化

跳层的代价是真实的:跳过指标和链路直接抓 CPU,大概率抓到的是无关业务的编解码线程。跳过剖析直接改代码,就是"我觉得这里慢"的占卜开局。

分层框架:架构 > 算法 > 并发 > 参数 > 硬件 ​

层收益上限典型动作改动风险
架构10×~100×加缓存、异步化削峰、读写分离、消灭跨机房/循环 RPC高:要方案评审 + 灰度
算法/数据结构10×(热点路径上)O(n²)→O(n log n)、命中索引、容器与数据布局选型中
并发数倍串行 RPC 并行化、请求合并批处理、线程模型中:引入竞态与超时语义变化
参数10%~50%池大小/超时/批大小/GC 选项/内核参数低
硬件<线性NVMe、升配、加核花钱,且掩盖问题

投入产出排序规则:收益上限从上到下递减,改动成本也递减。所以决策不是"从下往上摘软柿子",而是——profile 说热点在哪层,就先打哪层。热点是一次 40 万行的全表扫描时(算法层),去调 JVM 参数(参数层)是安慰剂。热点是循环里 200 次跨机房 RPC 时(架构层),换更快磁盘(硬件层)是买拐杖治骨折。

帕累托:80% 的耗时来自 20% 的热点 ​

  • 耗时分布天然不均:80% 的总耗时集中在 20% 的接口/SQL/代码路径上。找到它们只要三张清单:慢查询 Top10、接口耗时 Top10、火焰图最宽塔。
  • 每次只打最大头,打完重新测量。消掉一个热点后,第二名会升上来,且瓶颈类型可能换层(SQL 快了之后暴露锁等待)。分层框架要重走一遍。
  • 帕累托也是止损依据:Top 清单打进长尾区(每项占比 <5%)时,剩余优化空间进入摊薄区。投入产出开始劣化,应转向固化与防退化。
  • 清单要定期重算而不是一次性:流量结构随业务演化(新功能上线、渠道占比变化)。上季度的热点清单这季度可能已经失真——慢查询/慢接口 Top 清单按周自动生成,进稳定性周报。

走层案例:一次下单链路优化的三刀 ​

text
 起点:下单 P99 = 1.1s,SLO 承诺 500ms。基线入库,开始走层。

 第1刀(架构层):链路 trace 显示串行调 3 个无依赖下游
   (风控 200ms / 库存 190ms / 优惠券 210ms)
   → 三次 RPC 并行化:串行 600ms → 并行 220ms,P99 降到 640ms
 第2刀(算法层):重新剖析,新最大头 = 优惠券查询 80ms/次
   → EXPLAIN 全表扫描 → 补联合索引 + 覆盖索引:80ms → 3ms,P99 降到 420ms
 第3刀(参数层):并行化后连接池等待计数上涨(新瓶颈浮出)
   → 池 50→120、下游超时对齐其 P99:P99 降到 380ms,达标收手

 要点:每一刀之前都重新测量——第1刀后瓶颈从"RPC 串行"(架构层)
 换成了"全表扫描"(算法层),第2刀后又换成"池排队"(参数层)。
 热点会换层,工具与手法跟着换,分层框架是地图不是一次性的 checklist。

分层速查:每层的高频手法与库内延伸 ​

层高频手法排查/验证工具库内延伸
架构缓存、异步化削峰、读写分离、消灭循环/跨机房 RPC链路 trace 看调用次数与跨度缓存穿透-击穿-雪崩、多活容灾与单元化(跨机房 RT 账)
算法索引命中、复杂度降阶、批量化、数据布局EXPLAIN、火焰图数据库怎么分库分表、索引
并发串行调用并行化、请求合并、线程模型jstack 多次对比、Arthas thread -b问题定位步骤 现场保留一节
参数池大小、超时、批大小、GC 选项指标大盘、GC 日志、压测对比观测性三件套
硬件盘/核/网卡升级iostat、sar、全链路压测重校水位3+1保障(容量水位)

一次完整优化的叙事模板 ​

text
 现象:下单 P99 从 800ms 涨到 3.2s(量化 + 时间点 + 对齐变更时间线)
   ↓
 测量:指标定位到下单接口;链路 Top span = 库存查询 1.8s;
       剖析 = 新增查询条件无索引,全表扫描 40 万行/次(证据链闭合)
   ↓
 假设:"X 变更导致 Y 路径劣化,表现为 Z"——因果链必须解释全部现象
   (P99 涨而 P50 不动 ✓ 长尾特征;错误率 0 ✓ 纯读慢;对齐发布时间 ✓)
   ↓
 验证:代价从低到高——EXPLAIN 复现 → 预发加索引 → 灰度 1% 新旧实例
       同时刻 A/B 对比 P99(排除时段流量差异)
   ↓
 固化:慢 SQL 门禁进 CI、性能基线入库、索引纳入巡检;
       大变更(切库/升配)后重建基线(见 [数据迁移与切库方案](数据迁移与切库方案.md))

叙事模板的价值不在"汇报好看",在于它强制每一步可证伪。假设写出来就可能被推翻,验证不通过就回滚假设而不是硬上。固化一步保证优化成果不随人员流动退化。它同时是述职/晋升里"性能优化"叙事的验收标准——还原不出这五段的优化故事,在评审上不成立。

追问:为什么分层框架把架构层排在最前,收益最大就一定先做吗?

不是按收益排,按"profile 指认的热点所在层"排。架构层收益上限最高,但若热点只是缺一个索引,上缓存架构是杀鸡用牛刀,还引入一致性复杂度。分层的真正含义是"下刀前先知道自己在哪一层、收益上限与风险各是什么"。最贵的错误不是选了低收益层,是用底层的廉价手段反复试探高层问题。例如调参治跨机房 RT、扩容治慢 SQL。

反模式 ​

  • 过早优化:Knuth 的完整语境是"不应在 97% 的时间里纠结那 3% 的热点"。不是"禁止优化",是"没有测量数据支撑的优化先不做"。启动期为想象中的百万 QPS 设计缓存分层,交付的是复杂度不是性能。
  • 无基线优化:改前没记数,改后"感觉快了"。感觉不能进复盘,也不能进晋升材料。
  • 多变量齐改:同时动缓存、参数、代码,好了不知道谁的功,坏了不知道谁的锅。这和 问题定位步骤 的"一次只改一个变量"是同一条纪律。
  • 局部最优全局变差:单接口加缓存把下游缓存打穿。把定时任务从凌晨调到白天"提高资源利用率"挤爆白天 IO。优化必须回到全局大盘验证。
  • 扩容当优化:加机器掩盖慢 SQL 与泄漏,成本线性上涨而问题指数积累。容量是最后一层,见分层框架最底行。
  • 一次性优化:不设门禁不建基线,半年后新代码把老热点重新引进来。没有固化步骤的优化是租来的。
  • 为想象流量优化:当前 QPS 500,为"将来可能的百万"上多级缓存与异地单元。容量问题用容量手段解(压测摸水位 + 弹性扩容),只有测量证明瓶颈真实存在,才进入优化流程。
  • 只优化不定义慢:没人写下"P99 多少算慢、服务谁、什么场景",团队就永远在优化。SLO 先行,优化是对 SLO 的收敛,不是无目标的手艺活。

追问链 ​

面试官:怎么证明是你的优化让接口变快了?

灰度 A/B:同一时刻新旧版本实例各承接一部分真实流量。对比 P99/P99 差值,排除时段、流量结构、机器差异。再跨一个业务周期(工作日/周末)重复验证趋势稳定。改前基线 + 同时刻对照,两个条件缺一个,因果都不成立。

面试官:优化到什么时候停?

三个停止条件取最先到达:SLO 达标(P99 满足业务承诺即停,不为简历多打 20ms)。边际收益劣化(热点清单打进长尾,每项 <5%)。机会成本(同样人力投稳定性/交付收益更大)。性能优化没有终点,但有明确的止损线——写不出下一步收益预估时就该停。

面试官:优化完怎么防止退化?

三道防线:慢 SQL/性能门禁进 CI(回归阶段拦截,超基线阈值不允许合并)。性能基线入库,版本间自动对比(P99 漂移 >10% 告警)。周期性压测巡检(大促前全链路压测重校水位,见 3+1保障)。退化的主要通道不是"老代码变慢",是"新代码重新引入老模式"——所以门禁比记忆可靠。

一句话总结 ​

测量定位(指标选面、链路选段、剖析定点)→ 按层下刀(架构>算法>并发>参数>硬件,打 profile 指的那层)。→ 帕累托排序逐个消灭热点 → A/B 验证 → 门禁固化。 优化的专业性不体现在知道多少招,体现在每一步都有数字兜底。

速查附录:四步套路 ​

收纳自早期速查版《性能优化套路》(原文已并入本篇)。适合排查时照着走一遍的最短路径:

  1. trace 定位:通过实际流量复现一次高耗时调用,拿到 traceId。
  2. RPC 耗时排序:按 traceId 拉全链路。统计链路各系统的 RPC 服务耗时与系统间交互耗时。再检查各系统连接耗时是否异常。最后将各 RPC 服务耗时按优先级排序,找最大头。
  3. 分层下刀(对应上文分层框架):
    • 缓存——减少数据库访问次数与耗时计算量:线程内 invokeCache、分布式缓存 Redis、页面缓存这类手段;
    • 多线程——增强处理速度,如单线程改多线程;
    • 请求合并——减少循环调用次数,合并后一次转发;
    • 异步化——缩小数据处理范围(减少查询内容、分页异步加载),逻辑简化。非核心逻辑走消息中间件解耦异步化。
  4. 复现验证:改完再次复现问题,观察优化后性能是否符合预期。不符合则回到第 1 步重新定位。

2026-10 新增(工程方法论系列)。