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