3.7 层级选择与成本
上一节:3.6 破坏性测试 | 下一节:3.8 Lab 3 配套代码:03-test-pyramid/07-selection-and-cost
一句话结论
压测环境本身就是钱(机器、数据准备、人力、时间),而不同层级的成本差几个数量级。能用便宜层级回答的问题,绝不用贵的层级——这不只是省钱,更是为了迭代速度,而迭代速度直接决定优化质量。
一、用「风洞试验按小时计费」理解成本
汽车研发里:
| 试验 | 成本 | 一天能做几次 |
|---|---|---|
| 台架试验 | 几百元/小时 | 十几次 |
| 转鼓试验 | 几千元/小时 | 几次 |
| 风洞试验 | 上万元/小时 | 一次 |
工程师的直觉:能用台架回答的问题,绝不进风洞。
因为:风洞一次要几小时,你一天只能验证一个假设;台架一次几分钟,你一天能验证二十个假设。假设验证次数直接决定你找到正确解的概率。
后端完全一样。
二、成本对照表
| 层级 | 单次耗时 | 环境需求 | 数据准备 | 一天能做的轮次 |
|---|---|---|---|---|
| ① 微基准 | 秒级 | 一台开发机 | 无 | 上百 |
| ② 组件基准 | 几十秒 | 一台开发机 + 容器 | 一次性准备 | 几十 |
| ③ 集成基准 | 几分钟 | 一台机器 + DB/缓存 | 一次性准备 | 十几次 |
| ④ 全链路负载 | 20–60 分钟 | 完整环境 + 独立压测机 | 每次可能都要准备 | 2–5 |
| ⑤ 浸泡测试 | 1–12 小时 | 独占环境 | 一次性 | < 1 |
| ⑥ 容量测试 | 数小时 | 接近生产的拓扑 | 完整 | < 1 |
看第 4 行和第 2 行的对比:全链路一天只能试 2–5 个假设,组件基准能试几十个。
这就是「先用组件基准定位,再用全链路验收」的真正理由:不只是省钱,而是全链路的迭代速度太慢,慢到无法支撑「试错式定位」。
三、层级选择决策树
问题是什么?
│
├─ 「这段代码/算法快不快?」
│ → ① 微基准(JMH)
│
├─ 「这条 SQL / 这个序列化 / 这个缓存操作慢不慢?」
│ → ② 组件基准
│
├─ 「单个服务完整处理一个请求要多久?连接池/事务有没有问题?」
│ → ③ 集成基准
│
├─ 「满足 SLO 吗?拐点在哪?副本要几个?」
│ → ④ 全链路负载 + ⑥ 容量测试
│
├─ 「有没有内存/连接泄漏?」
│ → ⑤ 浸泡测试(至少 1 小时)
│
├─ 「系统垮了会怎样?能自愈吗?」
│ → ⑤ 破坏性测试
│
└─ 「为什么慢?」(不知道哪里慢)
→ 剖析(火焰图)→ 定位后再选层级验证
注意最后一条:「为什么慢」永远先做剖析,而不是先压测。 剖析告诉你「在哪」,压测告诉你「多少」。
四、什么时候「不该测」
这是一个很少有人讨论、但很重要的判断:
| 情况 | 为什么不测 | 该做什么 |
|---|---|---|
| 功能还没正确 | 性能数据无意义,而且要重复测 | 先让功能正确 |
| 瓶颈已经明确(比如已知是缺索引) | 不需要测量,直接修 | 直接修,然后测「修复效果」 |
| 优化收益的理论上限很小 | 假设某操作只占总延迟 2%,优化 50% 也只省 1% | 先做延迟分解,找大头的部分 |
| 没有稳定的测试环境 | 数据不可用,白花时间 | 先解决环境问题(或改用组件基准) |
| 效果无法被用户感知 | P99 从 210 ms 降到 205 ms | 把时间花在别处 |
最后一条特别重要:性能工作容易变成「数字游戏」。判断标准始终是「用户能不能感知」和「是否解决了 SLO 违约」。
五、测试预算怎么分配
给一个参考比例(按投入时间算):
| 层级 | 建议占比 | 理由 |
|---|---|---|
| 微基准 | 10% | 只在算法选型和热点优化时用 |
| 组件基准 | 40% | 性价比最高,应该成为日常工具 |
| 集成基准 | 15% | 验证单服务完整链路 |
| 全链路 | 20% | 验收 SLO、容量规划 |
| 浸泡 + 破坏性 | 15% | 上线前的长稳与极限验证 |
注意组件基准占 40%——它应该是你最常用的工具,而不是「偶尔测一下」。
如果你的团队目前只有「上线前压一次全链路」,那么最大的改进空间就是把组件基准建起来:它能在几分钟内回答过去需要几小时才能回答的问题。
六、一个实用的判断流程
遇到性能任务时,按这个顺序问自己:
① 这个问题「在哪一层会第一次显现」?
↓
② 那一层的单次成本是多少?我一天能试几轮?
↓
③ 如果成本太高(< 5 轮/天),能不能降一层做近似验证?
↓
④ 无法降层时(比如必须验证 SLO),确保交付物明确:
「达标/不达标 + 拐点 + 崩溃形态 + 容量建议」
第 ③ 步是最实用的技巧。例子:
- 要验证「新加的缓存对 P99 的影响」→ 不必上全链路,组件基准就能测出缓存命中/未命中的延迟差异。
- 要验证「序列化器替换的收益」→ 微基准 + 组件基准即可。
- 要验证「连接池大小」→ 集成基准足够。
只有「排队与跨服务放大」这一类问题,才必须上升到全链路。
七、本节小结
- 压测环境是成本,不同层级差几个数量级。
- 全链路一天只能试 2–5 个假设,组件基准能试几十个——迭代速度直接决定优化质量。
- 层级选择的核心问题:「这个问题在哪一层会第一次显现?」
- 「为什么慢」永远先做剖析,不是先压测。
- 学会说「不该测」:功能未正确、瓶颈已明确、理论上限很小、环境不稳定、用户无法感知。
- 组件基准应该占你 40% 的测试投入——这是大多数团队最大的改进空间。
八、自测
- 你要验证「把 JSON 序列化器从 Jackson 换成 kotlinx.serialization」的收益。该用哪一层?为什么不用全链路?
- 团队说「我们没有独立的压测环境,所以在开发机上压一下」。请给出两种可行的替代方案,并说明各自的局限。
- 一个优化任务的理论上限是「最多能省 1% 的 P99」。你应该怎么做?
- 用微基准 + 组件基准。理由:① 序列化器替换是一个纯计算 + 分配的变化,微基准(JMH)能精确测出两种序列化器的吞吐与
gc.alloc.rate;② 再用组件基准测「真实对象结构下的序列化开销」以及它在接口总延迟中的占比;③ 不需要全链路——因为序列化的成本不依赖网络、排队、跨服务交互,全链路只会把这点差异淹没在噪声里,而且一次要几十分钟。唯一需要全链路的情况:如果序列化改变了 payload 大小,从而影响了网络传输(大响应体),那才需要端到端验证。 - 方案一:只做组件基准与集成基准——用 Testcontainers 起真实数据库,在开发机上测单服务。局限:测不到网络、网关、排队、多副本;结论只适用于「相对比较」,不能用于容量规划。方案二:用容器资源限额模拟生产规格——在开发机上用 Docker 限制 CPU/内存(
--cpus=1 --memory=2g),至少让「容器 CPU 节流」这个常见陷阱被复现。局限:仍然无法模拟网络位置与多服务拓扑。推荐的组合:日常优化用方案一,SLO 验收与容量规划则必须争取到独立环境——因为这两件事的结论直接影响线上决策,用开发机数据做决策是不负责任的。 - 应该先不要做这个优化,或者先扩大搜索范围。理由:① 1% 的上限意味着即使完美实现也无法被用户感知,而实现成本(开发、测试、引入复杂度)是确定的;② 更值得做的是先做延迟分解(第 1 章 1.3 节),找出占 P99 大头的环节(通常是某个依赖或排队),那里的优化空间是几十个百分点而不是 1%;③ 例外情况:如果这个 1% 是「当前唯一剩余的、成本可控的改进」,且系统已经非常优化(比如已经在 P99 的临界点上),那它可以作为收尾。但优先级永远排在「找大头」之后。