文档目录

3.4 负载曲线:阶梯、恒定、尖峰

上一节:3.3 集成基准与全链路 | 下一节:3.5 浸泡测试 配套代码:03-test-pyramid/04-load-profiles


一句话结论

三种负载曲线用来发现三种不同的问题:阶梯加压找拐点,恒定压测验证稳态 SLO,尖峰测试验证弹性与恢复。用错曲线,就会测不出你关心的问题。


一、用汽车测试理解三种路况

路况 负载曲线 测什么
匀速巡航 恒定到达率 稳定工况下的油耗与舒适度
逐步加速到极限 阶梯加压 最高能跑多快、什么时候开始抖动
急加速 + 急刹车 尖峰测试 刹车性能、恢复能力、热衰减

汽车厂商三种都测,因为它们暴露的问题完全不同。后端也一样。


二、阶梯加压(Ramping):找拐点

长什么样

到达率
  ↑
2000 ┤                                    ╭──── 崩溃点
1000 ┤                        ╭───────────╯
 700 ┤              ╭─────────╯  ← 拐点
 400 ┤      ╭───────╯
 100 ┤──────╯
     └──────────────────────────────────────→ 时间
       每级:爬升 30s + 稳态 2~5min

关键参数(每一项都有理由)

参数 建议值 为什么
起始负载 目标容量的 10%~20% 先建立低位基线
每级增幅 1.5–2 倍 太大跳过了拐点,太小耗时太久
爬升时长 30 秒 让系统有时间升到该级(不要瞬间跳变)
稳态时长 2–5 分钟 ⚠️ 最关键:队列要建立、缓存要进入混合状态、GC 周期要走过至少一轮
终止条件 崩溃线(第 1 章 1.4 节) 避免把服务压死

最容易犯的错:每级只跑 30 秒

为什么不行:

  • 刚升上去时队列是空的,延迟短暂偏低 → 你会误判「这个负载很轻松」。
  • 缓存可能还是热的 → 命中率虚高。
  • 完全覆盖不到周期性的 GC 停顿与缓存失效。
  • 长尾请求还没出现 → P99 虚低。

结果:曲线整体偏低且平坦,你找不到拐点,甚至以为容量还有很大余量。


三、恒定到达率(Constant):验证稳态 SLO

长什么样

到达率
  ↑
500 ┤    ┌────────────────────────────────┐
    │    │      稳态测量窗口(5~10 分钟)    │
    └────┴────────────────────────────────┴──→ 时间
         预热(不计入统计)

用途

  • 验收 SLO(第 1 章 1.4 节):在目标 QPS 下,P99/P999/错误率是否达标。
  • 建立基线(第 1 章 1.6 节):作为后续优化的 before。
  • 回归对比:改代码后用同一曲线复跑。

关键要求

要求 为什么
预热阶段独立且不计入统计 否则混入冷启动数据(第 2 章 2.4 节)
稳态时长 ≥ 5 分钟 覆盖 GC、缓存、连接池周期
用到达率模型而非虚拟用户模型 防协调遗漏(第 0 章 0.6 节)
验证实际 QPS == 设定 QPS 否则压测机是瓶颈,数据不可用
记录错误率 只看延迟会漏掉「把慢请求变成错误」的假优化

四、尖峰测试(Spike):验证弹性与恢复

长什么样

到达率
  ↑
3000 ┤        ╭──╮
 500 ┤────────╯  ╰────────────────────────
     └──────────────────────────────────────→ 时间
          瞬间跳升      回落

用途

尖峰测试只回答一个问题:流量突然暴涨时,系统会不会崩,以及崩了之后能不能自己恢复?

它会暴露这些问题:

现象 说明
队列积压后无法消化 尖峰过去后延迟仍然很高(积压的请求还在处理)
重试风暴 客户端超时重试,把已经过载的系统压得更狠
连接池雪崩 所有连接被慢请求占住,新请求全部超时
缓存击穿 尖峰导致大量缓存未命中,直接打到数据库
自动扩容跟不上 扩容需要几分钟,尖峰只有几十秒
恢复时间很长 尖峰结束后需要多久回到正常水平?

关键设计

要点 说明
尖峰前要有基线负载 从 500 → 3000,而不是从 0 → 3000(后者测的是冷启动)
尖峰持续时间要短 10–60 秒,模拟真实突发
必须观察恢复阶段 尖峰结束后继续跑 2–5 分钟,看能否回到基线
记录恢复时间 这是容量规划的重要输入

注意:尖峰测试经常会让服务真的崩掉。请确保: ① 有明确的终止条件;② 不影响生产;③ 准备好重启流程。


五、三种曲线对照表

阶梯加压 恒定到达率 尖峰测试
回答 能扛多少?拐点在哪? 达标吗? 突发时会怎样?能恢复吗?
时长 20–60 分钟 10–20 分钟 5–10 分钟
关键输出 拐点 QPS、崩溃点 P50/P95/P99、错误率 恢复时间、崩溃形态
典型发现 哪类资源先饱和 SLO 是否达标 重试风暴、连接池雪崩
对应章节 第 1 章容量曲线 第 1 章 SLO 本章第 6 节破坏性测试

六、三个通用的设计错误

❶ 用虚拟用户模型跑所有曲线

// ❌ 闭环:服务变慢时自动降压,掩盖问题
export const options = { vus: 100, duration: '5m' };

// ✅ 开环:按计划发压,服务端慢也不减量
export const options = {
  scenarios: { s: { executor: 'constant-arrival-rate', rate: 500, timeUnit: '1s', duration: '5m' } }
};

❷ 不验证「实际到达率」

每次压测都必须做的第一件事:

设定的到达率 : 500 req/s
实测的 QPS   : 480 req/s
差 4% → 可接受

设定的到达率 : 1000 req/s
实测的 QPS   : 620 req/s
差 38% → ❌ 压测机是瓶颈(preAllocatedVUs 不足 / CPU 打满),数据作废

❸ 不预热就开始测量

即使是恒定压测,也要有独立的预热阶段——否则前 30 秒的冷启动数据会污染 P99。


七、本节小结

  1. 阶梯加压找拐点,关键是每级 2–5 分钟稳态(太短会误判)。
  2. 恒定到达率验证 SLO,必须预热分离 + 验证实际到达率 + 记录错误率。
  3. 尖峰测试验证弹性与恢复,必须观察恢复阶段,而不只是尖峰本身。
  4. 所有曲线都要用开环(到达率)模型,否则会被协调遗漏污染。
  5. 压测第一步永远是验证实际 QPS 是否等于设定值——不相等则数据作废。

八、自测

  1. 你做了阶梯加压:100 → 500 → 1000 → 2000 RPS,每级只跑 30 秒。结果曲线很平坦,P99 一直在 50 ms 以内。你判断「单实例能扛 2000 RPS」。这个结论有什么问题?
  2. 尖峰测试中,流量从 500 跳到 3000 后回落。你该观察哪些指标来判断「系统恢复了吗」?
  3. 为什么阶梯加压的每级增幅建议是 1.5–2 倍,而不是 10 倍或 1.1 倍?
  1. 问题有三层:① 每级 30 秒太短——队列还没建立、缓存还是热的、GC 周期没走完,所以延迟普遍虚低,曲线当然平坦;② 因此没有找到真正的拐点,「能扛 2000 RPS」这个结论缺乏依据(真实的拐点可能在 600,只是被 30 秒的窗口掩盖了);③ 即使 2000 RPS 时指标看起来正常,也没有应用 headroom,更没有验证崩溃点在哪。正确做法:每级维持 2–5 分钟,重新压一次,观察 P99 是否在某一级开始非线性上升。
  2. ① P99 是否回到尖峰前的水平(这是最直接的判据);② 错误率是否归零;③ 恢复时间(从尖峰结束到指标回落,用了几秒还是几分钟);④ 饱和度指标是否回落——连接池 pending 是否回到 0、线程池/调度器队列深度是否清空、容器 CPU 节流是否停止;⑤ 是否有积压残留(如果是队列型系统,队列长度是否降回基线);⑥ 内存是否回落(如果内存没降,可能缓存被尖峰撑大了,或者有泄漏)。特别注意:如果 P99 回到了正常但 pending 还 > 0,说明系统仍在排队,只是暂时缓解。
  3. ① 太大(10 倍):会直接跳过拐点——你可能从「完全正常」一步跳到「已经崩溃」,无法确定拐点在哪一级;而且巨大的负载跳变本身会造成非典型的冲击(相当于一次尖峰测试),测出的不是稳态容量。② 太小(1.1 倍):要达到 10 倍负载需要几十级,每级 3 分钟就是几小时,成本过高;而且每级的差异太小,会被测量噪声淹没,无法判断变化是真实的还是噪声。③ 1.5–2 倍是「能定位拐点」与「总耗时可控」之间的折中:10 倍负载只需 4–5 级,每级之间又有明显差异。