主题
界面
主题
《JMeter 压力测试建模》讲了怎么压;本节讲怎么判断压出来的是好数据还是坏数据:
压测跑完了,屏幕上一堆数字 ——
哪些能信?哪些是假的?哪些指标会互相骗人?三个最致命的问题:
| 问题 | 后果 |
|---|---|
| 压测没带断言 ✗✗✗ | 压的是 500 错误页,QPS 特别漂亮 |
| 只看平均值 ✗✗ | 尾延迟被掩盖,用户实际等了 5 秒 |
| 失败请求算进 QPS ✗✗ | 数字虚高,掩盖了故障 |
先看一个真实的"漂亮报告":
压测结果:平均响应时间 12 ms,QPS 4200,错误率 0%
结论:系统性能优秀 ✓而实际上:
| 被藏起来的事实 | 怎么藏的 |
|---|---|
| 接口返回的是 500 | 没加断言 → 只要"有响应"就算成功 ✗✗✗ |
| 有 3% 的请求超时 | 超时的没算进去(只统计了"完成的")✗ |
| P99 是 8 秒 | 报告只有 Average ✗ |
这份报告不是"错的",而是"选出来的" ✗ ——这是压测最危险的失败模式 ✓✓
这是本节的第一条铁律。
没有断言的压测:
JMeter 认为"收到 HTTP 响应 = 成功" ← 默认行为 ★
所以:
· 返回 500 → 也算成功 ✗✗✗
· 返回业务错误码 → 也算成功 ✗✗
· 返回空内容 / 错误页面 → 也算成功 ✗✗后果:一个"所有请求都失败"的接口,压测报告可以非常漂亮 ✗
接口坏了 → 立刻返回 500(不做任何业务处理)→ 响应极快 → QPS 极高
压测报告:"QPS 8000,平均 1.2ms,错误率 0%" ← 全都是假的 ✗✗✗所以必须加断言(JMeter 里的 Response Assertion):
| 断言 | 检查什么 |
|---|---|
| 响应码断言 | 200 / 201(按接口约定)★ |
| 响应体断言 | 含关键字,或 JSON 字段值正确 ✓ |
| 响应时间断言 | 超过阈值直接判失败(压测里很有用)✓ |
| 响应大小断言 | 防止"空响应也算成功" ✓ |
★ 一个简单有效的做法:
断言:"响应码 = 200 或 201" + "响应体包含 orderNo"这样"接口坏了"会立刻表现为"错误率飙升" ✓ ——而不是"性能优秀" ✗
本项目的压测工具就是这么做的(LoadTestLesson.send):
if (response.statusCode() >= 400) {
throw new IllegalStateException("HTTP " + response.statusCode()); // ★ 计入失败
}先看一组数据:
100 个请求:99 个 10ms + 1 个 5000ms
Average = 59.9 ms ← "还行"
P50 = 10 ms ← 一半用户比这快
P95 = 10 ms ← 95% 的用户很快 ✓
P99 = 5000 ms ← ★ 1% 的用户等了 5 秒 ✗✗✗
Max = 5000 ms平均值的三个问题:
| 问题 | 说明 |
|---|---|
| 掩盖尾部 ★★ | 1 个坏样本被 99 个好样本"稀释" ✗ |
| 没有"用户视角" | 用户不关心平均,他关心"我这次快不快" ✓ |
| 不可比较 | 两个系统可能平均值相同、分布完全不同 ⚠️ |
各分位的含义与用途:
| 分位 | 含义 | 用来看 |
|---|---|---|
| P50(中位数) | 一半请求比它快 | "典型用户"的体验 ✓ |
| P90 | 90% 比它快 | 一般慢用户 |
| P95 ★★ | 95% 比它快 | SLA 最常用(如"P95 < 200ms")✓ |
| P99 ★★★ | 99% 比它快 | 尾延迟——最容易出问题的地方 ✓✓ |
| Max | 最慢的一次 | 可能是 GC / 网络抖动 / 冷启动 ⚠️ |
★ 为什么 P99 特别重要:
一个日活 10 万的系统,P99 就是每天有 1000 个用户遇到"最差体验" ✓
而且"最慢的请求"往往对应最复杂的业务场景(大订单、复杂查询)——恰恰是最重要的用户 ✓✓
本轮的实测数据正好印证(T2-003 第 7 节):
并发 64:QPS 68.8,P50 = 845 ms,P99 = 2011 ms如果只报平均值:读者会以为"约 1 秒",而实际上"有 1% 的用户等了 2 秒" ✗
先问一个问题:QPS 里的"请求",包不包括失败的?
| 口径 | 数字 | 后果 |
|---|---|---|
| 含失败 | 数字好看 ✗ | 掩盖故障 ✗✗✗ |
| 只算成功 ★ | 数字诚实 ✓ | 能反映真实可用吞吐 ✓ |
★ 关键认识:
失败的请求往往比成功的"更快" ——因为它没干活 ✗
所以"含失败的 QPS"会虚高 ——系统越坏,数字越漂亮 ✗✗✗
正确做法:
① QPS 与错误率分开报
② 用"成功 QPS"作为容量指标
③ 错误率超过阈值(如 0.1%)→ 这次压测的 QPS 数字作废 ✗两个必须一起看的指标:
| 组合 | 含义 |
|---|---|
| QPS 上升 + 错误率 0 | 健康 ✓ |
| QPS 上升 + 错误率上升 | "虚高"——实际有效吞吐在下降 ✗✗ |
| QPS 平台 + 延迟上升 | 到拐点了 ★(T2-003 的 5.5) |
| QPS 下降 + 错误率上升 | 过载,系统在崩溃 ✗✗✗ |
JMeter 的 Aggregate Report 字段:
| 字段 | 含义 | 注意 |
|---|---|---|
# Samples | 总请求数 | 含失败 ⚠️ |
Average | 平均响应时间 | 不要只看它 ✗ |
Median | P50 | ✓ |
90% Line / 95% Line / 99% Line ★★ | 分位延迟 | 重点看这三列 ✓✓ |
Min / Max | 最小 / 最大 | Max 可能是异常值 ⚠️ |
Error % ★★ | 错误率 | 必须与 Throughput 一起看 |
Throughput | 每秒完成请求数(含失败) | ⚠️ 注意口径 |
Received KB/sec | 吞吐的带宽 | 大响应体时是瓶颈 ✓ |
★ 读报告的顺序(这个顺序很重要):
① 先看 Error % —— 错了就不必往下看了 ✗
② 再看 Throughput —— 到多少
③ 再看 95%/99% Line —— 用户感受到多慢
④ 最后看 Average/Max —— 参考为什么这个顺序:
错误率高的数据是"无效数据" ——先排除它 ✓
然后看吞吐(能扛多少),再看延迟(代价多大) ✓
平均值放最后——它只是一个参考 ✓
本节最值得记住的一条——它来自本轮实测的一个真实发现 ✓
我的压测工具在结尾自动打了一段"解读":
===== 3. 怎么读这几行数据 =====
吞吐最高出现在并发 = 64(QPS=69)
从 1 并发到 4 并发:QPS 上升,说明还有余量 ← ★ 这句话是错的 ✗✗
继续加压如果 QPS 不再上升、而 P95 继续涨 —— 就到了「拐点」,系统开始排队而实际数据是:
并发 QPS P50
1 55.1 14
4 61.1 57 ← QPS 只涨 11%,而 P50 涨了 4 倍 ✗
16 68.7 213
64 68.8 845★ "还有余量"是错的——真实情况是"并发 1 时就已经接近饱和" ✓✓
为什么会写错:那段解读是"写死的模板文案" ✗ ——它只判断"QPS 有没有涨",没看"涨了多少、代价是什么" ✗
这条教训极其重要:
工具的"自动结论"是最容易骗人的东西 ✗✗
因为:
- 它的判断逻辑是固定的(写死在代码里)
- 它不知道你的业务上下文(11% 的提升算不算"有余量"?取决于代价)
- 它会给人"我已经读过数据了"的错觉 ⚠️
所以:工具只该负责"呈现数据",结论必须自己下 ✓✓
正确的读法(对比着看三个量):
| 该看什么 | 本轮数据 | 结论 |
|---|---|---|
| QPS 涨幅 vs 并发涨幅 ★ | 并发 ×64,QPS ×1.25 | 严重不成比例 → 已饱和 ✗ |
| 延迟涨幅 vs 并发涨幅 ★★ | 并发 ×64,P99 ×36 | 延迟爆炸 → 加并发是有害的 ✗✗ |
| 错误率 | 0% | 没崩,只是慢 ✓ |
★ 教学价值:这段错误的自动解读我保留在代码里了 ✓ ——它比任何"正确示范"更能说明问题 ✓
一份可用的压测报告(缺任何一项,报告就不可信):
| 项 | 内容 |
|---|---|
| ① 环境 ★★ | 机器配置、部署方式、压测机与被测服务是否分离、网络 |
| ② 被测目标 | 哪个接口、什么数据、是否带参数 |
| ③ 压测模型 | 并发、时长、思考时间、Ramp-up(T2-003 四要素) |
| ④ 断言 ★★ | 凭什么判定"这次请求成功" |
| ⑤ 结果 | 成功 QPS + P50/P95/P99 + 错误率(四件套)✓ |
| ⑥ 资源水位 ★ | 服务端 CPU / 内存 / GC(T2-016) |
| ⑦ 结论与有效范围 ★★ | "在什么环境下、什么负载区间内,这个结论成立" |
★ 第 ⑦ 条最容易被忽略:
性能结论是"有条件的" ✓ ——"P95 < 200ms" 必须附上"在 50 QPS 以内" ✓
脱离条件的性能数字没有意义 ✗
JMeter → HTTP 请求 → 添加 → 断言 → 响应断言
Field to Test: Response Code
Pattern: 200|201见 5.4 的"读报告顺序" ✓
见第 4 节 ✓
实测工具:labs/web-basics/src/main/java/cn/kb/learn/web/perf/LoadTestLesson.java
它的自动解读(错的):
从 1 并发到 4 并发:QPS 上升,说明还有余量 ← ✗ 与数据不符数据本身(对的):
并发 总请求 QPS P50(ms) P95(ms) P99(ms)
1 40 55.1 14 39 55
4 160 61.1 57 119 187
16 640 68.7 213 393 517
64 2560 68.8 845 1594 2011正确的解读(三句话):
| # | 结论 | 依据 |
|---|---|---|
| ① | 吞吐上限约 69 QPS | 并发 16 → 64,QPS 68.7 → 68.8(零增长)★ |
| ② | 超出后延迟爆炸 | P99 从 55ms → 2011ms(36 倍)✗✗ |
| ③ | 系统没有崩溃,只是排队 | 错误率全程 0% ✓ |
★ 把"工具的结论"与"数据本身的结论"摆在一起,就是本节最好的教材 ✓✓
| # | 错误 | 说明 |
|---|---|---|
| 1 | 压测不带断言 ✗✗✗ | 压的是错误响应(5.1)✗✗✗ |
| 2 | 只报平均值 ✗✗ | 掩盖尾延迟(5.2)✗ |
| 3 | 把失败的算进 QPS 且不报错误率 ✗✗ | 粉饰(5.3)✗ |
| 4 | 拿"工具的自动结论"当结论 ✗✗ | 见 5.5 ★★ |
| 5 | 报告不写环境与压测参数 | 不可复现、不可比较 ✗ |
| 6 | 拿一次压测的数字当"永久容量" | 结论有条件、有有效期 ⚠️(代码/数据量变化后要重测) |
目标:亲手制造一次"漂亮的假数据",再看清真相。
步骤:
RawServlet 让它返回 500(临时),跑压测 → 观察 QPS 与错误率 ✗;验收方式:能说出第 1、2 步两次压测的差别在哪、为什么。
扩展任务:给压测工具加"分档停止条件":
Samples=10000, Average=15ms, 99%Line=4200ms, Error=0.2%, Throughput=1200/s,这份数据有什么问题?LoadTestLesson 的输出加上"成功率"与"成功 QPS"两列,并说明在过载时这两列与总数差多少。《JMeter 参数化与关联》:让每个虚拟用户用不同的数据、并能"顺着上一个响应"发下一个请求。
暂无