文档目录

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 的影响」→ 不必上全链路,组件基准就能测出缓存命中/未命中的延迟差异。
  • 要验证「序列化器替换的收益」→ 微基准 + 组件基准即可。
  • 要验证「连接池大小」→ 集成基准足够。

只有「排队与跨服务放大」这一类问题,才必须上升到全链路。


七、本节小结

  1. 压测环境是成本,不同层级差几个数量级。
  2. 全链路一天只能试 2–5 个假设,组件基准能试几十个——迭代速度直接决定优化质量。
  3. 层级选择的核心问题:「这个问题在哪一层会第一次显现?」
  4. 「为什么慢」永远先做剖析,不是先压测。
  5. 学会说「不该测」:功能未正确、瓶颈已明确、理论上限很小、环境不稳定、用户无法感知。
  6. 组件基准应该占你 40% 的测试投入——这是大多数团队最大的改进空间。

八、自测

  1. 你要验证「把 JSON 序列化器从 Jackson 换成 kotlinx.serialization」的收益。该用哪一层?为什么不用全链路?
  2. 团队说「我们没有独立的压测环境,所以在开发机上压一下」。请给出两种可行的替代方案,并说明各自的局限。
  3. 一个优化任务的理论上限是「最多能省 1% 的 P99」。你应该怎么做?
  1. 用微基准 + 组件基准。理由:① 序列化器替换是一个纯计算 + 分配的变化,微基准(JMH)能精确测出两种序列化器的吞吐与 gc.alloc.rate;② 再用组件基准测「真实对象结构下的序列化开销」以及它在接口总延迟中的占比;③ 不需要全链路——因为序列化的成本不依赖网络、排队、跨服务交互,全链路只会把这点差异淹没在噪声里,而且一次要几十分钟。唯一需要全链路的情况:如果序列化改变了 payload 大小,从而影响了网络传输(大响应体),那才需要端到端验证。
  2. 方案一:只做组件基准与集成基准——用 Testcontainers 起真实数据库,在开发机上测单服务。局限:测不到网络、网关、排队、多副本;结论只适用于「相对比较」,不能用于容量规划。方案二:用容器资源限额模拟生产规格——在开发机上用 Docker 限制 CPU/内存(--cpus=1 --memory=2g),至少让「容器 CPU 节流」这个常见陷阱被复现。局限:仍然无法模拟网络位置与多服务拓扑。推荐的组合:日常优化用方案一,SLO 验收与容量规划则必须争取到独立环境——因为这两件事的结论直接影响线上决策,用开发机数据做决策是不负责任的。
  3. 应该先不要做这个优化,或者先扩大搜索范围。理由:① 1% 的上限意味着即使完美实现也无法被用户感知,而实现成本(开发、测试、引入复杂度)是确定的;② 更值得做的是先做延迟分解(第 1 章 1.3 节),找出占 P99 大头的环节(通常是某个依赖或排队),那里的优化空间是几十个百分点而不是 1%;③ 例外情况:如果这个 1% 是「当前唯一剩余的、成本可控的改进」,且系统已经非常优化(比如已经在 P99 的临界点上),那它可以作为收尾。但优先级永远排在「找大头」之后。