做过压测的人都遇到过这种场面:第一次跑 QPS 3200,第二次跑 2800,第三次又变成 3500,同样的命令、同样的参数,结果却像抽奖。这时候很多人开始怀疑工具、怀疑机器"坏了",其实绝大多数情况是测试过程中混进了太多变量。性能测试的难点从来不是"把命令敲对",而是"控制住那些悄悄变化的因素"。
这篇文章系统梳理压测结果不稳定的原因,分成外部原因、被测服务原因和测试方法原因三类,再给出让结果可复现的具体做法,以及该采集哪些数据来定位问题。文中数字为经验量级,方法比数字更重要。
一、不稳定是常态,先别急着怀疑工具
先建立一个正确认知:性能测试结果的波动是正常的,完全相同的数字才是可疑的。CPU 睿频、后台任务、网络抖动、垃圾回收,都会让数字上下浮动。经验上,同一套条件下 QPS 波动在 ±5% 以内,可以认为基本稳定;波动超过 ±15%,就说明有变量没控制住,需要排查。
排查的思路是"控制变量":一次只改变一个条件,其余全部固定,这样才能知道到底是谁在影响结果。
二、外部原因
1. 压测机自己先成了瓶颈
这是最经典的坑。压测工具本身要消耗 CPU、内存、网络和端口资源。如果压测机的 CPU 跑满了、网卡打满了、或者没调大文件句柄和端口范围,那么你测到的不是服务端能力,而是压测机的极限。
判断方法:压测时同时看压测机的 CPU、网络和 ss -s。如果压测机 CPU 接近满,很可能结果被它拖累了。
# 压测机侧观察
top -bn1 | head -20
ss -s
# 调大可用端口范围(临时),避免客户端端口耗尽
# 注意:这是内核参数,修改需谨慎,建议只做临时测试用
sysctl net.ipv4.ip_local_port_range
2. 网络抖动
即使是内网,交换机、虚拟网络、共享带宽也会有抖动。跨境或公网测试更明显,晚高峰和白天可以差很多。网络一抖,TCP 重传增加,响应时间上升,QPS 就掉。
3. 测试环境不一致
- 机器不同:每次压测用的是同一批机器吗?CPU 型号、频率、核数一致吗?
- CPU 频率不同:睿频状态、BIOS 的省电策略会让主频浮动。
- 超线程/NUMA 设置不同:同一进程绑到不同 NUMA 节点,结果差异可观。
- 虚拟化差异:虚机所在宿主机的负载、是否超额售卖,都会影响结果。
- 同网段其他流量:测试机房里别的业务在跑,会抢占资源。
三、被测服务自身的原因
1. GC 停顿
Java、Go 等有垃圾回收的语言,GC 是压测波动的最大来源之一。QPS 曲线周期性下凹、响应时间周期性尖峰,多半是 GC。压测时要采集 GC 日志,观察停顿频率和时长。
# Java 示例:开启 GC 日志(JDK 8 及以前)
java -Xloggc:/var/log/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar app.jar
# JDK 9+ 统一日志
java -Xlog:gc*:file=/var/log/gc.log:time -jar app.jar
2. 缓存冷热
刚启动的服务,本地缓存、连接池、JIT 编译都还没热,前几十秒性能明显差;跑一会儿热起来了,数字又上去。冷启动和热稳态的结果完全不同,混在一起看就会"忽高忽低"。
3. 预热不足
连接池、数据库连接、线程池、索引缓存,都需要预热。不预热直接压测,前段数据不可用。
4. 后台任务与定时任务
日志轮转、定时备份、监控采集、数据同步等后台任务,如果在压测期间触发,会瞬间抢资源。压测时要避开或暂停这些任务。
5. 锁竞争与资源争抢
并发一高,锁竞争加剧,响应时间非线性上升,QPS 不升反降,波动也变大。这类问题需要结合线程栈分析,后面章节会讲。
四、测试方法本身的问题
1. 测试数据在变化
每次压测访问的数据不同,命中率就不同。有缓存的接口,第二次压测时缓存已经被第一次填满了,结果自然更好。这是"越测越快"或"越测越慢"的常见原因。
2. 并发控制不当
- 并发加得太猛,服务直接过载,数据剧烈波动。
- 并发阶梯不合理,没有找平台期。
- 压测时长太短,前几十秒还在预热,测的是"启动阶段"性能。
经验上,单轮压测至少持续 1~3 分钟,让系统进入稳态,剔除前段预热数据再统计。
3. 监控缺失
只记录 QPS,不看资源,出了问题无从判断。没有监控的压测等于盲测,结果不可信也无法优化。
4. 测试脚本本身有开销
压测脚本如果自己做了复杂计算、日志打印、随机生成大对象,会额外消耗压测机资源,间接影响结果。
五、让结果可复现的做法
| 措施 | 具体做法 |
|---|---|
| 固定环境 | 每次用同样的机器、同样配置、同样部署 |
| 环境隔离 | 压测环境独立,不与别的业务混跑 |
| 充分预热 | 正式测前先跑 30~60 秒预热,丢弃预热数据 |
| 多次取中位数 | 跑 3~5 轮,取中位数,而非均值或单次 |
| 随机化数据 | 用多组不同但等量的测试数据,避免缓存偏差 |
| 分离压测机与被测机 | 压测机独立且资源充足,确保自己不是瓶颈 |
| 固定时长 | 每轮时长一致,覆盖稳态区间 |
| 关闭无关任务 | 压测期间停掉定时任务、后台备份 |
| 记录完整指标 | 客户端 + 服务端 + 系统层一起采集 |
一次只改一个变量,是这些措施背后的统一原则。
六、该采集哪些数据
压测结果要"三头对照":
客户端(压测机):
- QPS、响应时间(平均、P95、P99)、错误率。
- 压测机自身的 CPU、网络、端口使用。
服务端应用:
- 应用日志、GC 日志、线程池/连接池使用率。
- 业务指标:缓存命中率、数据库查询耗时、下游调用耗时。
系统层:
# CPU、上下文切换、内存、IO 概览
vmstat 1
# CPU 各维度(用户态、系统态、软中断、等待)
sar -u 1
# 磁盘 IO(await、%util、IOPS)
iostat -x 1
# 网络连接与监听
ss -s
# 进程级 CPU/内存排序
top -bn1 | head -20
把这些数据按时间对齐,就能看到"QPS 掉下去的那一刻,谁在涨"。比如 QPS 掉、%sys 涨、上下文切换暴涨,多半是锁竞争或系统调用开销;QPS 掉、GC 停顿变长,是垃圾回收;QPS 掉、await 涨,是磁盘。
常见问题
Q1:跑几次结果都不一样,以哪次为准?
不要选"最好看"的一次,也不要只看均值。跑 3~5 轮,看中位数和波动范围,报告里写明波动区间,才客观。
Q2:为什么第二次压测比第一次快?
很可能是第一次把缓存预热了,或者 JIT 编译完成了。要区分"冷结果"和"热结果",正式测试前先预热,并保持每轮测试数据一致性。
Q3:压测机和服务端放同一台可以吗?
强烈不建议。同一台机器上两者抢 CPU 和网络,测出来的数字没有意义。至少分成两台,压测机资源要留足。
Q4:怎么判断是压测机成了瓶颈?
压测期间看压测机的 CPU、网络、ss -s。如果压测机 CPU 接近满、端口大量 TIME_WAIT、网络打满,而服务端资源还有富余,基本就是压测机瓶颈。
Q5:波动大但不知道原因怎么办?
先用监控三头对照,按"CPU/内存/磁盘/网络/GC/锁"逐个排除。每次只改一个变量复测,直到定位。没有捷径,就是控制变量法。
总结
- 压测结果波动是正常的,控制变量是核心方法,一次只改一个条件。
- 外部原因:压测机自身成瓶颈、网络抖动、环境不一致;被测原因:GC、缓存冷热、预热不足、后台任务、锁竞争;方法原因:测试数据变化、并发与时长不当、监控缺失。
- 让结果可复现的九件事:固定环境、隔离、预热、取中位数、随机化数据、分离压测机、固定时长、关闭无关任务、完整采集指标。
- 数据要三头对照:客户端看 QPS/响应/错误,服务端看应用日志和 GC,系统层看 vmstat/sar/iostat/ss。
- 单轮时长至少 1~3 分钟并剔除预热段;波动 ±5% 内可接受,超过 ±15% 必须排查。
企业QQ咨询




