做性能测试这么多年,经常遇到第一次接触压测的同事跑完一轮就兴奋地拿着JMeter聚合报告里的Average和Throughput来汇报:“响应时间200ms,TPS 300,稳了!”结果上线第二天就被真实流量打出一身冷汗。其实响应时间平均值、吞吐量这些只是最表面的几个数字,如果不懂它们之间的关系,不知道哪些指标才是压测的关键,测试报告就只能是一堆自欺欺人的数字游戏。这里我打算把性能测试中常见的指标从头到尾捋一遍,说清楚每个指标到底测什么、怎么读、怎么和场景关联。无论你是刚开始做压测的测试工程师,还是需要分析线上容量问题的后端开发,看完这篇至少不会再把TP90和平均响应时间搞混,也不会在看到CPU没打满时就盲目说系统有瓶颈。
1. 先理清性能测试到底在测什么
1.1 性能指标的本质
性能测试不是“用工具压一下接口,看返回快不快”这么简单。它的本质是做一次受控的实验:给系统施加一个明确的负载,然后观察系统在这个负载下表现出的数字。这些数字就是性能指标。指标的意义在于把“快”和“慢”、“好”和“坏”从抽象感受变成可以量化的数据。
正因为性能指标需要支撑后面的容量评估、故障定位、代码调优,所以它必须可测量、可重复、可对比。如果一次压测跑出来的响应时间是200ms,另一次同样的压力跑出来是2s,那这两组数字背后一定有变量发生了变化,可能是环境变了、代码变了、数据量变了,也可能是测试方法本身有问题。这才是性能测试真正要回答的问题:在什么条件下,系统的能力边界在哪里。
从用户视角和系统视角两个维度看,指标可以分成两大类。用户能感知到的是外部指标:响应时间、吞吐量、错误率。系统内部运行状况是内部指标:CPU使用率、内存占用、磁盘IO、线程池状态、GC停顿时间。外部指标告诉你“系统行不行”,内部指标告诉你“为什么行,或者为什么不行”。我见过不少性能测试报告只写了外部指标,资源和后端状态完全是黑的,这种报告拿去排查问题时几乎没有任何指导价值。
1.2 指标之间的关联关系
常有人说性能指标太多了,记不住。我建议你不要死记指标名称,先把它们之间的关系理顺。可以拿一条城市道路来做类比。
并发用户数是路上的车流量,单位时间通过路口的车辆数就是吞吐量(TPS),每辆车从路口一头开到另一头的耗时就是响应时间,车辆之间发生碰撞或者抛锚的比例就是错误率。车少的时候,车都能顺畅通过,每辆车耗时短,但总通过量不高。随着车流增加,单位时间通过路口的车变多,车辆通行速度开始变慢。到了路口的饱和承载量,再多车也过不去了,通过的车辆数到达峰值。如果继续强行往路口塞车,排队越来越长,通行时间急剧增加,碰撞和事故也开始变多。
这个类比基本就是系统性能曲线的缩影。压测过程中,我们会看到响应时间随着并发升高而变大,TPS先线性增长,然后增速放缓,最后持平甚至掉头向下,错误率则在拐点附近开始冒头。理解了这条曲线的形状,很多指标不再孤立。它就是同一个负载变化下,系统外部表现的一组连锁反应。
1.3 如何设定可度量的测试目标
性能测试必须在开始之前定义“什么是达标”。没有目标的压测,最后拿到一堆数字也不知道该不该高兴。目标通常以SLA(服务等级协议)的形式写下来,例如某个订单查询接口:
| 负载条件 | 响应时间要求 | 吞吐量要求 | 错误率要求 |
|---|---|---|---|
| 200并发持续压测15分钟 | TP95 ≤ 500ms | TPS ≥ 300 | ≤ 0.1% |
这个SLA包含三层信息:负载大小(200并发)、持续时间(15分钟)、量化阈值(响应时间、吞吐量、错误率)。缺了任何一项,后续都无法评判结果。目标也不是拍脑袋定的,通常来自两类依据:一类是业务预期,比如产品经理要求高峰期所有列表页在1秒内展示;另一类是现有系统的基线表现,比如线上平稳状态下平均响应时间300ms,那压测目标可以定在TP95≤500ms留出余量。
2. 功能维度:响应时间、吞吐量、错误率,别只看平均值
2.1 响应时间:平均值会骗人,百分位才是真相
响应时间是最直接的用户感知指标,定义是从客户端发出请求到收到完整响应所花费的时间。听起来简单,但在统计响应时间时,很多人会掉进平均值陷阱。举个例子:一个接口跑了100个请求,99个请求耗时100ms,只有1个请求耗时10s,平均响应时间是(99×0.1+10)/100≈199ms。从平均值看,系统响应很快,但实际体验是那1个请求的用户已经等到抓狂。平均响应时间被极少数慢请求拉高或者拉低,掩盖了真实的质量情况。
正确做法是看百分位响应时间,也就是TP50、TP90、TP95、TP99这些指标。它们的含义是把所有请求的响应时间从小到大排序,TP95就意味着有95%的请求响应时间小于等于这个值。比如TP95=500ms,代表绝大多数用户都能在500ms内得到响应。压测报告里应优先看TP90和TP99,这两个数字对长尾问题非常敏感。
我曾经排查过一个在线支付回调接口,平均响应时间一直稳定在250ms左右,但线上经常有人反馈支付结果通知很慢。后来在压测时单独看了TP99,发现达到了2.8秒。顺着TP99去翻日志,定位到偶发的数据库连接池等待和一次Full GC停顿。如果只看平均值,这类问题会一直藏在水下。所以实测中我建议在聚合报告里同时记录Avg、TP90、TP95、TP99,而且判断达标与否以TP95或TP99为准,平均值只作为参考。
2.2 TPS与QPS:单位时间内到底能处理多少请求
吞吐量代表系统单位时间内能够处理的请求数量,常见单位是Requests/sec。后来在业务侧又出现了TPS和QPS这两个词。TPS是事务每秒,QPS是每秒查询数。很多人在混用它们,但其实需要区分业务语义。
对于简单HTTP接口,一次请求通常等于一个事务,此时TPS和QPS数值上可以看成等价。但对于复杂业务则不成立。典型如下单业务,客户端一次点击“提交订单”,后端可能同时调用用户校验接口、库存扣减接口、订单写入接口、消息推送接口。如果以整个“下单”为事务,一次成功下单产生了几次内部HTTP查询,那么TPS统计的是下单次数,QPS统计的是所有内部接口被调用的次数。两者数值可能相差3到5倍。所以写测试目标时,一定要先约定清楚本次测的是“用户事务”还是“后端查询请求”。
JMeter聚合报告里有个Throughput字段,单位是requests per second。它统计的是所有Sampler的请求数,不是业务事务数。如果你的脚本里一个业务环节包含了3个HTTP请求,JMeter的Throughput会比真实的业务TPS高3倍。这就是为什么不能直接把JMeter里的Throughput当成业务TPS写进报告。需要在脚本层面设计好业务逻辑或者二次计算,一个问题场景对应一个Sampler或者一个Transaction Controller。
2.3 错误率:比响应时间更先暴露问题
错误率等于失败请求数除以总请求数。但要搞清楚什么叫“失败”。HTTP层面来说,4xx和5xx都意味着请求异常,但业务上可能4xx是正常的参数校验不通过,5xx才是系统故障。JMeter默认只统计连接失败、超时这类IO错误,HTTP状态码只要不是网络异常,它都会当作成功。如果不在脚本里配置响应断言,很可能5xx响应都被记成成功,错误率保持0%,和真实情况完全背离。
给HTTP请求加一个简单的响应断言,判断响应码是2xx,是这个基础操作中最重要的一步。需要更严格时,可以断言响应体里包含某个业务成功字段。例如接口返回JSON里code=0表示成功,那么就把断言设为“code=0”,这样业务失败也才会被计入错误率。这一步做完,错误率才有意义。
错误率与系统过载有很强的相关性。多数系统在接近容量拐点时,错误率会从0突然上升到几十个百分点,可能是连接超时、线程池拒绝、数据库连接耗尽。同时也会看到响应时间急剧飙升。如果压测中发现错误率先于响应时间出现异常,通常说明系统已经出现排队溢出或者资源关闭。此时要尽快减小并发,保护测试环境,也方便排查具体错误类型。
3. 资源维度:CPU、内存、磁盘与网络的观测要点
3.1 硬件指标的采样姿势
性能测试只看应用返回的数据是不完整的,还要同时观察被压测服务所在主机的资源消耗。因为只有资源指标能告诉你系统的瓶颈在哪里。常用工具有top、vmstat、iostat、sar、nmon。压测开始后每隔1到2秒记录一次采样,持续整个测试周期。不能只看采样结束时的均值,要观察变化趋势。
CPU使用率要区分user、system、iowait。user高说明程序在密集计算,通常是代码算法问题;system高说明内核态消耗很大,常见原因有系统调用过于频繁、上下文切换过多、网络包处理、中断等。iowait高表示CPU在等待磁盘IO,往往意味着磁盘读写成了瓶颈。比如在压测中看到active线程不多,但iowait一直超过50%,就要去检查日志写入量、临时表空间、数据库落盘策略。
内存指标不要只看总内存还有多少,重点看是否大量使用swap。一旦发生swap,内存页在磁盘和内存之间换进换出,响应时间会出现几十毫秒到几百毫秒的毛刺。free命令显示swap的used不为0,或者vmstat里si/so频繁出现较大的数值,内存已经不够用了。频繁的GC和内存抖动经常被误判成CPU高,实际上持续分配对象导致GC占用了大量CPU。
磁盘IO在传统机械盘上比较好判断,%util接近100%往往意味着饱和。换成SSD和NVMe之后,%util的意义变得模糊,因为SSD即使%util为100%也可能还有处理余量。更好的判断方式是看读写队列长度是否持续增长,以及await时间是否明显高于正常基线。数据库服务器建议用iostat采集svctm和await,应用服务器如果日志量很大,也同样需要关注。
网络指标容易被人忽略,但高并发场景下,网卡流量和长连接数量会成为隐藏瓶颈。观察sar -n DEV里的rxkB/s、txkB/s是否超过网卡带宽的一半,检查网络连接数是否触及端口范围限制或文件句柄限制。压测中曾经遇到TPS到500就上不去,服务端CPU和内存都很正常,最后发现是TIME_WAIT连接数过多,新连接建立失败。这类问题不采网络指标基本看不出来。
3.2 数据库与应用服务器的隐藏指标
很多接口瓶颈不在应用代码,而在数据库。数据库层面需要关注连接数、慢SQL、锁等待、缓冲池命中率。MySQL可以用show global status查看Threads_connected是否接近max_connections;如果连接数打满,新的应用请求就会排队等待获取连接。慢SQL直接看慢查询日志,锁等待则用show engine innodb status查看事务状态。压测过程中如果TPS平稳但响应时间有规律地升高,很可能就是后台定时任务或者慢SQL占用了锁资源。
应用服务器自身也有几个关键指标:线程池活跃线程数、队列长度、GC次数与停顿时间。Java进程可以用jstat -gcutil观察YoungGC和FullGC发生的频率与耗时。如果Full GC频繁,JVM所有应用线程都会短暂停止,响应时间曲线上会出现明显的尖刺。线程池队列长度持续增长,说明处理速度赶不上请求速度,系统正在进入排队状态。Spring Boot默认的Tomcat线程池参数如果设置得不合理,前面功能指标还没到容量拐点,线程池就已经拒绝新请求了。
这些内部指标不是压测报告必须全部列出的数据,但它们服务于最终结论。比如发现响应时间从200ms涨到800ms,有没有GC停顿数据,直接决定了排查方向。没有资源数据,就只能猜。
3.3 资源指标与功能指标的联动分析
性能分析的基本逻辑是功能表现出现异常时,用资源指标来定位原因。反过来资源指标的异常,也能帮助你预测功能指标的变化趋势。我总结过一个比较简单的对照思路。
当TPS不再上升但CPU已经接近100%时,最合理的判断是CPU已经是瓶颈,系统所有线程都在忙于计算,吞吐量到达硬件上限。
当TPS上不去但CPU使用率不足30%时,瓶颈不在CPU,而在等待其他资源。常见的有数据库连接池等待、锁等待、下游接口调用慢、磁盘IO排队。这几类等待都会导致线程阻塞,CPU自然空闲下来。
当响应时间出现毛刺但平均响应时间正常时,优先看GC停顿、锁等待、网络重传。三种问题的解决路径完全不同,所以我才强调功能指标和资源指标必须同时采样,否则你无法从毛刺形态判断原因。
性能报告里如果只写“TPS最高达到800”,信息量太少。应该写“TPS在并发300时达到峰值800,对应CPU 70%,平均响应时间350ms,TP99 800ms,错误率0.05%,数据库连接池使用率90%”。到这一步,这个测试结论才具备参考价值。
4. 容量维度:并发用户数与SLA目标怎么定
4.1 并发用户不是在线用户
并发用户数是性能测试里被误解最多的概念。很多人以为系统有1000个在线用户,压测时就设置1000个并发线程。实际上在线用户是“挂着系统但可能什么都没做”的人,并发用户是“同时正在发出请求”的人。一个电商平台的10000个在线用户中,真正同时在浏览、点击、下单的可能只有几百人。并发度这个比例需要根据业务模型估算,不能拍脑袋。
JMeter的线程数也不完全等于同时请求数。如果一个线程组设置了1000个线程,Ramp-Up Period也给10秒,那JMeter是在10秒内逐步启动这些线程。前5秒可能只有几百个线程在跑,并不是1000个请求完全同时发生。如果需要精确模拟某个业务在同一个时刻有大量请求同时打出,需要在Sampler前添加同步定时器Synchronizing Timer,设置等待线程数等于预设并发数,让这些线程同时被释放。做秒杀、抢购这类瞬发场景时必须用同步定时器,否则压测请求会自然错开,测不到真正的并发冲击。
4.2 梯度加压与系统拐点判定
想测出系统最大容量,不能用固定并发跑一次就完事。我习惯于用梯度加压法:从10个并发开始,每5分钟增加10个或20个并发,持续观察TPS、响应时间、错误率,直到系统出现明显劣化。
梯度加压的好处是能画出完整的容量曲线。初期并发增加,TPS持续上涨;随后增速放缓,说明系统开始有排队;继续加压,TPS进入平台期,说明已经到了吞吐上限;再加压,TPS不升反降,同时错误率从0开始冒头,这个位置就是系统的饱和点。饱和点对应的并发数称为最大并发用户数,作为压力上限记录。最大并发数通常不适合作为长期稳定运行的推荐值,更推荐把TPS出现增长趋势放缓时的并发数作为最佳并发。
每个拐点不能只测一次。因为资源环境、数据缓存、数据库状态变化,第一次跑出来的拐点可能受偶然因素影响。至少要独立复测一次,两次结果趋势一致才能形成结论。正式容量评估场合,我会把每次阶梯的并发、平均TPS、TP99、错误率记录到一张压测汇总表里,一眼就能看出拐点在哪一段。
4.3 从指标到容量规划的换算思路
性能测试得到指标之后,还需要把结论落到容量规划上。有一个经常用到的估算关系:在稳定状态下,吞吐量约等于并发用户数除以平均响应时间,即TPS = 并发数 / 平均响应时间。如果线上目标峰值TPS是500,平均响应时间目标是0.2秒,那么理论上需要的并发用户数大约是500 × 0.2 = 100。这个数字能让你大概知道压测时要把并发设置在哪里。
但注意这个公式的前提是系统没有进入饱和区,响应时间不会随着并发增加而剧烈变化。真实系统中,并发升高后平均响应时间也会上涨,所以实际规划不能直接在目标响应时间上反推,而应该用压测曲线中接近目标TPS段的实测响应时间去估算。例如压测结果显示并发300时TPS为500,平均响应时间450ms,那么线上920TPS时并发就可能在500以上。预留30%到50%的buffer是容量规划的常见做法。
5. 实操部分:用JMeter跑一轮指标测试的要点
5.1 测试前的线程组与监听器配置
JMeter是最常用的压测工具之一,但用不对地方很容易产出失真数据。线程组里需要设置线程数、Ramp-Up Period和循环次数。这三个参数加起来决定了实际施压负载。Ramp-Up Period我一般按每秒启动2到5个线程去设置,比如100个线程Ramp-Up设30秒。如果Ramp-Up太短,启动瞬间会对服务端造成一次流量冲击,虽然能测出瞬发能力,但平均指标会被拉低。
脚本中的断言一定要配置完整。HTTP请求默认对状态码并不敏感,即使收到500也会被当作正常响应。添加响应断言并勾选“Response Code”等于200,或者检查响应体中的业务成功标记,这一步直接决定错误率是否可信。压测时间也不能太短,短到只有几分钟可能还没覆盖到缓存失效、连接池回收、线程池扩缩容这些动态行为。我建议稳定负载下至少持续10到15分钟,容量类测试可以到30分钟。
还有一点容易被忽略:JMeter的图形界面本身也会消耗压测机资源。如果在线程数较大时用GUI模式跑压测,大量采样数据和监听器绘图会让压测机CPU高企,导致请求发出变慢,最终结果被压测机自身拖累。正确的做法是把JMeter测试计划保存为JMX文件,然后用命令行模式执行。
5.2 聚合报告怎么看
命令行压测结束后,JMeter会生成CSV或JTL结果文件。用聚合报告监听器打开后,有几个字段要重点关注。
| 字段 | 含义 | 评估关注点 |
|---|---|---|
| Samples | 总样本数 | 样本太少结论不可靠 |
| Average | 平均响应时间 | 参考即可,不作为主要达标依据 |
| Min / Max | 最小/最大响应时间 | 最大值往往指向慢请求 |
| Std. Dev | 标准差 | 越大说明响应时间越不稳定 |
| Error % | 错误率 | 需要先确认断言配置无误 |
| Throughput | 每秒请求数 | 和业务TPS区分理解 |
| 90% Line | 90%的请求响应时间 | 排除了极端值,衡量大部分用户体验 |
| 95% Line | 95%的请求响应时间 | 目标通常看TP95 |
| 99% Line | 99%的请求响应时间 | 暴露长尾问题 |
报告里数值高得离谱的Max和极高的99% Line是重点排查对象。它们通常不是平均负载造成的,而是某个瞬间发生了GC、锁等待、网络重试、连接池耗尽。所以我会把压测机上的日志时间和服务端GC日志时间对齐,找到尖刺出现的具体秒级窗口。
5.3 命令行模式与辅助监控
JMeter命令行执行的基本命令是jmeter -n -t test.jmx -l result.jtl -e -o report_dir。-n表示非GUI模式,-l输出结果文件,-e -o生成HTML报告。这个命令跑出来的HTML报告里包含TPS趋势图、响应时间百分位图、错误率趋势图,比在GUI里打开聚合报告更直观。要分析某个时间窗口的数据,可以加一个简单筛选,也可以直接用Excel透视结果文件。
压测机和服务端的时间最好是同步的,否则定位问题时两边的日志对不上。压测过程中同步开启nmon或sar采集服务端CPU、内存、磁盘、网络指标,结束后生成数据文件。这些辅助数据和JMeter报告之间的时间轴对齐,整个问题链条就清晰了。
超时时间的配置也需要注意。在HTTP Request Defaults里合理设置连接超时和响应超时,比如连接超时3s、响应超时10s。没有超时会有一个致命的后果:下游服务长时间挂起时,JMeter请求会一直等待,线程被占住,错误率统计不出来,同时服务端的线程池也可能被打满。只有超时连接被释放,错误率才能反映真实的不可用状态。
6. 常见问题与指标排查速查表
6.1 响应时间抖动怎么排查
响应时间平均不高但TP99高,这种情况最常见的原因是偶发的外部依赖和系统内部暂停。按顺序排查:查看GC日志,看测试时间段内是否有Full GC;查看数据库慢查询和锁等待;检查压测机和服务器之间的网络是否存在丢包和重传;查看应用日志中是否有网络超时、连接池获取超时的异常。
还有一个容易被忽视的原因:压测进程和后端的定时任务、日志归档任务叠加。比如每天早上两点有日志压缩任务,恰好压测在这个时间点跑,响应时间曲线就会出现周期性的尖刺。推荐的做法是压测前先观察一段时间的系统基线资源使用,避开定时任务窗口,或至少把定时任务单独记录出来。
6.2 TPS上不去但CPU很低怎么分析
CPU低但TPS停滞,说明系统资源在等待而不是在计算。排查顺序可以这样:先看数据库最大连接数和活跃连接数,连接池打满时应用会阻塞在获取连接上;再看应用线程状态,用jstack抓线程栈,大量线程BLOCKED说明锁竞争或连接池等待,大量线程WAITING说明依赖下游响应;再检查磁盘IO的iowait和await,如果内存不足发生swap,CPU也会被迫长时间等待。
还有一个外部因素要考虑:被压测服务是否依赖第三方接口或者配额受限。比如调用了外部限流为每秒100次的支付服务,上游TPS再多也会被限制。此时需要看服务端线程是否大量处于RUNNABLE但没有任何进展,配合日志中第三方调用的耗时就能看得出来。
6.3 错误率突升的方向性判断
不同错误类型指向的根因完全不同。如果错误率上升且错误样本中基本都是HTTP 500,优先查应用日志和异常堆栈,考虑代码bug、数据库会话中断、磁盘满。如果错误样本显示连接超时或读超时,优先查服务端线程池、连接池、防火墙、负载均衡连接数。如果错误率上升但HTTP状态码都是200,同时断言失败,往往是断言条件和业务返回值不一致,需要先确认返回体内容再判断是脚本问题还是真实业务失败。
| 指标现象 | 可能原因 | 优先排查项 |
|---|---|---|
| 响应时间整体上升 | 负载超过系统容量 | 资源使用率、队列、线程池 |
| TP99高但平均正常 | 偶发GC、锁、网络抖动 | GC日志、慢SQL、重传 |
| 错误率突升 | 服务端异常/超时/限流 | 应用日志、超时配置、连接数 |
| TPS停滞且CPU低 | 等待资源和下游 | 数据库连接池、锁、外部依赖 |
| TPS停滞且CPU高 | CPU计算密集 | 应用算法、GC、系统调用 |
| 磁盘IO高 | 日志写满/SQL排序/内存交换 | 磁盘队列、日志量、swap |
把这一套指标关联看下来,性能测试就不再是甩一串数字给开发就结束的事情。它其实是一个不断定位、排除、收敛的过程。我个人实际体会是,刚开始做性能测试时最容易犯的错就是只看JMeter报告里的基础字段,忽略百分位和资源指标,导致压测报告给出的结论既不完整也不可靠。后来养成“外部指标和内部指标同时记录、功能指标和资源指标联动分析”的习惯,很多以前要靠猜才能找到的问题,现在基本都能在测试阶段直接定位。如果你也想把性能测试做出真正决策价值,先从把指标看全、看透、关联起来开始。