0.8 性能工程闭环#
上一节:0.7 九条常见谬误自查清单 | 下一节:0.9 从 C++ 到 JVM:哪些直觉必须修正
一句话结论#
性能工作不是一个「上线前压一次」的动作,而是一个八步循环。 每一次优化后的回归数据,都会成为下一轮的基线。
一、八步闭环#
┌─────────────────────────────────────────────────┐
│ │
▼ │
① 定义目标(SLO) │
↓ │
② 建立模型(Little's Law / 队列估算) │
↓ │
③ 采集数据(压测 + 四层指标) │
↓ │
④ 剖析归因(火焰图 / JFR / 线程快照) │
↓ │
⑤ 定位根因(证据链 + 贡献占比) │
↓ │
⑥ 优化(按优先级) │
↓ │
⑦ 回归验证(统计显著 + 长稳) │
↓ │
⑧ 固化为门禁与监控 ─────────────────────────────────────────┘
为什么是循环而不是流程:第 ⑧ 步的监控数据会成为下一轮第 ① 步「目标是否需要调整」的输入;第 ⑦ 步的回归结果会成为下一轮第 ③ 步的基线。性能工作没有终点,只有「当前这一轮的稳定态」。
二、逐步拆解:每步做什么、产出什么、最容易在哪里失败#
① 定义目标(SLO)#
| 项 |
内容 |
| 做什么 |
把「快一点」翻译成「在 X 负载、Y 数据量下,Z 指标达到 W」 |
| 产出 |
SLO 表 + 延迟预算表 |
| 常见失败 |
目标没有负载前提(「P99 < 100 ms」在 1 QPS 和 10000 QPS 下是两个世界) |
| 对应章节 |
第 1 章 |
② 建立模型#
| 项 |
内容 |
| 做什么 |
用 Little’s Law 估算资源需求,用排队理论确定余量;在动手之前先有量级预期 |
| 产出 |
一张「预期容量 / 预期瓶颈」的假设清单 |
| 常见失败 |
跳过这一步直接压测,导致拿到一堆数字但不知道是否合理 |
| 对应章节 |
本章(0.4、0.5) |
这一步最容易被跳过,但它的性价比极高:如果算出来「需要 60 个连接」而你的池只有 10 个,那你连压测都不用做就知道问题在哪。
③ 采集数据#
| 项 |
内容 |
| 做什么 |
按设计的负载曲线压测,同时采集业务 / 延迟 / 资源 / 饱和度四层指标 |
| 产出 |
原始数据(曲线、火焰图、GC 日志、线程快照、DB 统计) |
| 常见失败 |
只采客户端数据、闭环模型、不预热、数据量不真实、单次运行 |
| 对应章节 |
第 3、4、5 章 |
④ 剖析归因#
| 项 |
内容 |
| 做什么 |
在拐点负载下采集 on-CPU / wall-clock / 分配 / 锁四类数据 |
| 产出 |
「时间花在哪条调用链上」的证据 |
| 常见失败 |
在低负载下剖析(什么都看不出来);只看 CPU 火焰图(漏掉等待类瓶颈) |
| 对应章节 |
第 4、6 章 |
⑤ 定位根因#
| 项 |
内容 |
| 做什么 |
把现象拆成可证伪的假设,逐个用命令验证,算出贡献占比 |
| 产出 |
按贡献排序的瓶颈清单 + 证据链 + 被排除的假设 |
| 常见失败 |
把相关性当因果(CPU 高 ≠ CPU 是原因,它可能是结果);没有贡献占比,无法排序 |
| 对应章节 |
第 6 章 |
⑥ 优化#
| 项 |
内容 |
| 做什么 |
按优先级:不必要 → 算法/IO → 并发架构 → 缓存 → 常数 → JVM 参数 |
| 产出 |
每一项带「收益 / 代价」的变更 |
| 常见失败 |
顺序颠倒(先调 GC 参数去优化一个本该加索引的查询) |
| 对应章节 |
第 7 章 |
⑦ 回归验证#
| 项 |
内容 |
| 做什么 |
同环境、同脚本、≥3 轮对比;统计显著性判定;长稳(浸泡)验证 |
| 产出 |
带置信区间的收益报告 |
| 常见失败 |
只测性能不测长稳(优化引入缓慢泄漏);只给 after 不给 before |
| 对应章节 |
第 5、7 章 |
⑧ 固化为门禁与监控#
| 项 |
内容 |
| 做什么 |
微基准纳入 CI;生产配置 SLO 与饱和度告警;更新容量表 |
| 产出 |
自动化的防退化机制 |
| 常见失败 |
门禁阈值设在噪声以内 → 天天误报 → 被所有人忽略 |
| 对应章节 |
第 8 章 |
三、谁在哪个阶段介入#
性能不是一个人的事。下表帮你判断什么时候需要拉谁进来:
| 阶段 |
你需要谁 |
为什么 |
| ① 定义目标 |
产品 / 业务 |
只有他们知道「多快算够用」「峰值是多少」 |
| ② 建立模型 |
你自己 |
这一步不需要别人 |
| ③ 采集数据 |
运维 / SRE |
需要独立的压测环境与真实的机器规格 |
| ④ 剖析归因 |
你自己 + DBA |
数据访问类瓶颈需要看执行计划和锁等待 |
| ⑤ 定位根因 |
你自己 |
这是最需要技术判断的一步 |
| ⑥ 优化 |
模块负责人 |
改别人的代码需要协作 |
| ⑦ 回归验证 |
你自己 + QA |
功能正确性与性能要同时回归 |
| ⑧ 固化为门禁 |
平台 / CI 团队 |
流水线配置通常不在你的权限内 |
四、这个闭环在现实中怎么被打破#
三种常见的「断环」方式,以及后果:
| 断在哪 |
表现 |
后果 |
| 跳过 ①②,直接 ③ |
「先压一下看看」 |
拿到一堆数字,无法判断是好是坏,也不知道该改什么 |
| 跳过 ④⑤,直接从 ③ 到 ⑥ |
「P99 高,加个缓存吧」 |
优化了非瓶颈,指标没变,还引入了新复杂度 |
| 跳过 ⑦⑧ |
「优化完了,上线」 |
收益无法证明;三个月后同一个人再优化一遍同一个问题 |
注意中间那一条:「跳过剖析直接优化」是最高频的错误。它会让你在错误的地方努力,而且因为确实做了一些「看起来专业」的改动,团队会以为问题已经解决。
五、本节小结#
- 八步闭环:定义目标 → 建立模型 → 采集数据 → 剖析归因 → 定位根因 → 优化 → 回归验证 → 固化为门禁。
- 它是循环:回归数据成为下一轮基线,监控数据提示目标是否需要调整。
- 最常被跳过的两步是 ② 建立模型(性价比最高)和 ④⑤ 剖析定位(决定优化是否有效)。
- 每一步都有明确的产出物;没有产出物的步骤等于没做。
六、自测#
- 一个团队的做法是:上线前用 VU 模型压 10 分钟,看 QPS 达标就发布。请指出他们走完了闭环中的哪几步、跳过了哪几步,以及最可能的后果。
- 为什么「② 建立模型」被说成性价比最高的一步?请举一个它能帮你省下大量时间的具体例子。
- 如果你只能说服团队补上闭环中的一步,你会选哪一步?为什么?
- 走完了 ③(采集数据,但方式有缺陷:闭环模型、无预热说明、单次运行);可能部分做了 ①(如果有 QPS 目标)。跳过了 ①②的模型估算、④⑤的剖析定位、⑥的针对性优化、⑦的统计回归、⑧的门禁固化。后果:QPS 达标但不知道瓶颈在哪、不知道拐点和余量、生产环境一旦数据量增长或流量突增就会出问题,而且下次还得从头再来一遍。
- 因为它能在动手之前排除掉大量可能性。例子:先用 Little’s Law 算出「1000 QPS × P99 60 ms = 最坏情况下 60 个并发请求」,而你发现连接池只有 10——那么不用压测就知道瓶颈在连接池排队。省下的是搭环境、写脚本、跑测试、分析数据的几个小时甚至几天。
- 我会选 ④⑤ 剖析定位。理由:跳过①②只会让数据缺乏解释框架,跳过⑦⑧会让成果退化,但跳过④⑤会直接导致优化错地方——既浪费了工程时间,又因为「做了优化」而掩盖了真正的问题,是最贵的错误。