服务器性能测试为什么结果不稳定?测试环境与数据采集分析

做过压测的人都遇到过这种场面:第一次跑 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% 必须排查。