
做了这么多年性能压测Apache Jmeter在我手里至少用了上千个小时一直有个体会压测工具本身只是用来制造压力的真正能回答“系统到底行不行”的是压力过程中被压机器的CPU、内存、磁盘、网络这四类指标。很多新人一上来就盯着Jmeter聚合报告里的吞吐量和响应时间跑完一轮数据挺漂亮可一旦问起服务端资源占用或者系统瓶颈到底在哪就答不上来了。这就是典型的“只会压、不会测”。这篇文章我会从一个完整的Apache Jmeter压力测试实践出发从方案设计、环境准备、脚本编写到执行压测再到CPU、内存、磁盘、网络的性能监控一条线全部串起来。不管你是在Windows上刚装好Jmeter还是已经在写接口压力测试脚本这篇内容都能给你一套可以直接照着做的思路和方法。最后还会整理我在实际项目里踩过的坑看完起码能帮你避开大半。1. 压力测试方案先搞清楚要压什么、压到多少算通过1.1 压测目标与场景建模QPS目标从哪来很多人做压力测试上来就打开Jmeter线程组随便填个100、200跑起来就算完事了。这样压出来的结果没有任何说服力。真正专业的做法是先想清楚两个问题第一这个接口平时有多少流量第二我们要压到的目标值是多少。先拿一个最常见的接口来举例订单查询接口。假设线上统计过这个接口日均调用量是3000万次而且流量并不是平均分布的白天8小时占了全天流量的绝大部分。那么高峰时段的平均QPS大概是QPS 日请求量 / 高峰秒数 30000000 / (8 * 3600) ≈ 1041再乘一个高峰波动系数按常见经验取2~3倍那这个接口的压测目标TPS可以定在2000~3000左右。这只是单接口的一个粗略估法如果是整条链路压测还需要把下游数据库、缓存、第三方依赖的容量一起考虑进去。定了目标之后才轮到场景建模。压测场景一般分三类单接口基准压测、混合场景压测、峰值稳定性压测。单接口压测用来摸清单个服务的处理上限混合场景模拟真实用户操作的比例比如订单查询60%、下单20%、支付20%峰值稳定性压测则是在最高并发下连续跑20~30分钟用来发现内存泄漏、连接池耗尽这类长时间才会暴露的问题。“压力测试怎么测”从来不是玄学按这三个场景分别设计才能把问题看透。1.2 压测方案要考虑的三件事数据、环境和隔离定好目标之后还要在方案里把数据、环境和隔离这三件事安排明白否则压测结果是不可信的。第一件事是测试数据。压测最怕的一件事就是用几条数据反复请求结果数据库缓存全被命中响应时间好看得离谱但一上真实环境立刻现原形。正确做法是准备一批与线上数据量级接近的测试数据通过CSV参数化或从数据库读取让请求尽可能分散落到不同主键上。还有一个常被忽略的点测试数据的分布要贴近真实比如订单状态有已完成、待支付、已取消比例也要模拟线上。第二件事是测试环境。压测机最好和被压服务分开部署不要让压测工具和被测服务抢同一台机器的CPU和内存。如果必须在测试环境压也要确保这个环境没有被其他团队共用不然资源监控数据会混入大量噪声。第三件事是变更控制。每次压测前记录下被测服务的版本、JVM参数、数据库连接池配置、Nginx配置。压测中发现瓶颈之后调优时只改一个变量复测一次这样定位问题和归因才清晰。1.3 为什么选Jmeter而不选其他压测工具市面上能做压力测试的工具不少ab、wrk、Locust、LoadRunner都有各自的场景。我大部分项目还是优先用Apache Jmeter不是因为它最强而是因为它在“功能全面”和“上手门槛”之间平衡得最好。工具协议支持脚本能力分布式压测上手难度费用Apache JmeterHTTP/HTTPS、JDBC、JMS、FTP等强支持BeanShell/JSR223支持中等开源免费ab仅HTTP弱只能简单并发不支持低开源免费wrk仅HTTP中等Lua脚本需要自己搭中等开源免费LocustHTTP为主强Python支持中等开源免费LoadRunner协议极多强支持高商业收费对大多数Web接口和业务系统来说Jmeter的HTTP取样器完全够用遇到需要连数据库做压测的直接用JDBC请求需要模拟复杂业务链路时有IF控制器、循环控制器、随机控制器这些逻辑组件可以组合。最关键的一点是Jmeter配合PerfMon插件可以做到压测和监控在同一套工具里完成后面我会详细讲这个方案。2. 环境准备与Jmeter安装配置2.1 Windows环境安装JDK与Jmeter先说Windows下怎么装。Jmeter依赖Java运行环境所以第一步是装JDK。Jmeter 5.x版本建议用JDK 8以上JDK 11或者JDK 17都行。安装JDK时记住安装路径装完后配置环境变量新建JAVA_HOME值是JDK的安装目录比如C:\Program Files\Java\jdk-17再把%JAVA_HOME%\bin加到Path变量里。配置完可以在命令行执行下面这段验证java -version能看到Java版本号就说明环境没问题。接下来去Apache官网下载Jmeter的zip包选最新的二进制版本比如apache-jmeter-5.6.x.zip。下载后解压这里有个很实用的建议解压路径不要带中文和空格否则后面执行脚本时可能出现莫名其妙的路径问题。我一般习惯解压到D:\tools\apache-jmeter-5.6这样的纯英文目录。进入解压目录的bin文件夹双击启动jmeter.bat。如果双击后没反应或者闪退先不要急着重装大概率是JAVA_HOME没配好或者JDK位数和Jmeter不匹配。64位系统装32位JDK也会导致启动异常。在命令行手动执行一次jmeter.bat看到报错信息再针对性处理这是排查启动问题最快的方式。2.2 启动Jmeter前的几个关键配置Jmeter默认启动参数比较保守脚本复杂或压测机本身配置不差时建议先调整一下JVM堆内存。打开bin目录下的jmeter.bat找到这段配置set HEAP-Xms1g -Xmx2g -XX:MaxMetaspaceSize256m想省事可以直接改成set HEAP-Xms2g -Xmx4g但要注意这个内存是给Jmeter自身用的开太大了会挤压压测机上的其他程序反而影响压测效果。我习惯的配置是压测机内存8G时给Jmeter分2~4G16G内存时给4~6G。另外要养成一个好习惯Jmeter的GUI模式只用来开发和调试脚本真正跑压力测试一定用命令行模式。原因很简单GUI模式本身就会消耗CPU和内存尤其开多个监听器时压测结果会被本机性能严重影响。在线程数超过50的场景我见过不少人在GUI里跑压测结果压测机自己先卡死的案例。命令行执行压测的标准姿势是这样jmeter -n -t test_plan.jmx -l result.jtl -e -o report_dir简单解释一下-n代表非GUI模式-t指定测试计划文件-l指定原始结果文件路径-e -o在压测结束后自动生成HTML报告到指定目录。后面案例部分我会再演示一次。2.3 准备被压接口和测试数据环境装好之后不要急着写脚本。先把被压接口的信息整理清楚包括接口地址、请求方法、请求头、参数格式、是否带Token鉴权。以订单查询接口为例它可能是这样的GET http://your-server/api/order/query/{orderId} Header: Authorization: Bearer token接口如果有鉴权需要在压测脚本里处理Token获取。常见做法有两种一是先用一个登录请求获取Token再用JSON提取器提取出来设置成全局变量供后续请求使用二是直接从测试环境拿到一个长期有效的Token放进HTTP信息头管理器里。前者更接近真实场景后者胜在简单稳定。我自己做压力测试时如果目标是测接口性能而不是测登录链路会用第二种方案避免登录鉴权成为瓶颈干扰判断。测试数据也要提前准备好。订单ID不能只有一两条否则数据库缓存命中率过高压出来的数据不真实。我会预先在数据库里造一批订单数据比如10万条状态各异的订单导出成CSV文件作为参数化数据源。3. 核心实操用Jmeter编写并执行接口压力测试脚本3.1 按这个步骤创建一个HTTP接口压测计划打开Jmeter GUI后左侧默认有一个“Test Plan”。右键点击它添加线程组然后在线程组上右键依次添加HTTP请求采样器、HTTP信息头管理器、响应断言、聚合报告。这是最基础的一组配置。点开HTTP请求采样器需要填的内容包括协议http或https、服务器名称或IP、端口号、请求方法、请求路径。如果接口是GET http://your-server/api/order/query/1001就填服务器名your-server路径/api/order/query/1001。实际压测时订单号不能写死这里先留个占位符后面参数化再替换。HTTP信息头管理器里加一行请求头Authorization: Bearer ${token}。这里的${token}是一个变量后面可以通过CSV数据文件或者在脚本里定义的变量来赋值。响应断言的作用是判断请求是否真的成功。比如接口设计为业务失败时返回{code:500}那就在响应断言里添加一个“响应文本”断言模式匹配code:200。这样即使HTTP状态码是200业务失败也会被统计为错误结果更真实。3.2 线程组参数怎么设并发数、Ramp-Up、循环次数线程组是压测的“总开关”这里有三个核心参数线程数、Ramp-Up Period、循环次数。线程数代表并发用户数。第一次摸底压测时不要一上来就500并发我习惯从50开始逐步往上加。Ramp-Up Period是线程启动耗时比如线程数100、Ramp-Up填10意思是10秒内启动完100个线程相当于每秒增加10个并发。这个值设得太小会导致启动瞬间压力突刺设得太大则压力曲线过于平缓都偏离真实情况。循环次数可以填具体数字也可以勾选“永远”然后通过调度器来限制压测时长。比较推荐用调度器方案勾选调度器填持续时间比如600秒这样每个线程会持续循环发送请求直到压测时间结束。对接口压力测试来说持续压测模式比固定循环次数更接近真实流量的形态。梯队加压是我每次压测必用的手法。比如计划分别压50、100、200三个档位我会准备三套线程组参数或者直接用Jmeter的Stepping Thread Group插件实现自动阶梯加压。第一轮50并发跑5分钟记录各项数据第二轮100并发第三轮200并发。每一轮结束都看一眼聚合报告和服务端监控确认没有异常再继续加压这样找到的瓶颈点才是可信的。3.3 参数化、关联和断言脚本不是写出来就能跑的很多新手写Jmeter脚本喜欢把请求参数写死。订单ID固定是1001用户ID固定是001跑下来的结果又稳定又好看可这个数据根本不能说明系统真实的性能水平。参数化的意义就是让每一次请求都带上不同的数据尽量模拟真实用户的行为。最常用的参数化方式是CSV数据文件。准备一个order_ids.csv文件里面每一行是一个订单号1001 1002 1003 ...在线程组上右键添加“配置元件 - CSV数据文件设置”填写CSV文件路径变量名称填orderId然后HTTP请求路径改成/api/order/query/${orderId}即可。关联在接口压测里也非常常用。比如登录接口返回一个Token后面下单、查询接口都要带这个Token。很多人不知道怎么处理直接用固定Token顶替。正确做法是在登录请求上添加JSON提取器配置一个变量名tokenJSON路径表达式填$.data.token再把HTTP信息头管理器里的Authorization设置为Bearer ${token}。这样每次压测都走真实的登录过程Token会动态获取。断言方面响应断言是最基础的更实用的是“持续时间断言”。比如接口要求P95响应时间低于500毫秒那就添加一个持续时间断言设置最大持续时间为500ms。这样凡是超过500ms的请求都会被标记为失败压测过程中就能实时看到有多少请求不达标。3.4 加思考时间压测数据真实性的一个关键细节真实用户不会像机器一样每秒钟不间断地疯狂点击接口他们看完一个页面再操作下一个中间总会有一个思考间隔。如果完全不加间隔压出来的是“极限压力”而不是“真实压力”。在脚本中可以通过添加“Constant Timer”固定定时器来实现思考时间。通常我会设置在1000~3000毫秒之间具体值取决于业务场景简单的按钮点击间隔可以短一些复杂页面浏览间隔要长一些。但这里有一个细节如果做的是容量摸底压测找系统极限那要不要加思考时间其实影响不大因为我们要测的是系统最多能扛多少QPS如果做的是模拟线上流量压测那就建议加上否则结果会偏高。我在方案设计时会把两种场景分开避免混在一起说不清楚。4. 性能监控CPU、内存、磁盘、网络一个都不能少4.1 为什么压测过程必须同步监控服务端资源只看Jmeter的响应时间和吞吐量做不了瓶颈定位。举个例子压测到200并发时响应时间从50ms涨到2000ms原因可能是服务端CPU被打满了也可能数据库连接池耗尽也可能是网络带宽到了上限。这三种情况处理方式完全不同不把CPU、内存、磁盘、网络的数据拉出来对比就只能靠猜。再加上一个更常见的场景响应时间很低吞吐量也很稳定但服务端CPU已经跑到95%了。这时候如果你继续加并发系统随时可能雪崩。所以资源监控不只是定位问题的工具更是压测过程中的“安全气囊”提前发现风险及时止损。4.2 轻量监控方案几条命令看完Linux服务器的四项指标如果只是摸底压测不想额外安装监控平台Linux自带的几个命令完全够用。看CPU最常用的命令是top执行后能实时看到CPU使用率、负载均衡、各进程CPU占用。vmstat 1可以每秒刷新一次输出进程、内存、分页、IO、CPU等系统的总体信息。很多人看到top命令输出里有一行类似下面这样的内容往往一头雾水%Cpu(s): 0.4 us, 0.2 sy, 0.0 ni, 99.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st我来拆解一下us是用户态CPU占用率指应用代码在跑sy是内核态CPU占用率指系统调用、进程调度等在跑ni是进程优先级调整过的占用id是空闲比例wa是CPU等待IO的时间hi和si分别指硬中断和软中断。如果wa很高说明磁盘或网络IO是瓶颈如果si很高说明网络包处理压力大。上面这组数据99.4%空闲说明CPU远远不是瓶颈真正该去查的是别的环节。内存用free -h看一眼就明白free -h重点关注的是available这一列它代表真正可用的内存而不是只盯着free列。内存够不够用要看的是系统还有多少余量以及有没有频繁使用Swap分区。如果Swap的使用率一直在增长说明物理内存很可能不足。磁盘IO用iostat -x 1iostat -x 1重点关注util列这个值接近100%时说明磁盘IO已经饱和再高的await说明IO请求排队时间很长。对数据库机器来说磁盘性能往往是最大的瓶颈。网络带宽用sar -n DEV 1查看sar -n DEV 1每条网卡都会输出两行rKB/s是每秒接收的KB数sKB/s是每秒发送的KB数。拿网卡的理论带宽比如千兆网卡约125MB/s和当前数值对比就能算出带宽的利用率。4.3 推荐方案Jmeter PerfMon插件做实时监控上面这些命令适合临时查看但压测过程中要持续观察并且把监控数据和压测结果放到同一张时间轴上对比效率最高的方案是用Jmeter的PerfMon Metrics Collector插件配合ServerAgent。ServerAgent需要部署在被压服务器上。下载对应的压缩包后解压Linux下执行startAgent.shWindows下执行startAgent.bat默认监听端口是4444。然后回到Jmeter通过Plugins Manager安装PerfMon插件添加一个监听器“jpgc - PerfMon Metrics Collector”在插件界面的Hosts列表里填被压服务器的IP、端口4444然后在Metrics列表里添加要监控的指标CPU、Memory、Disks I/O、Network I/O甚至还能监控TCP、JVM等。配置好后启动压测PerfMon监听器会实时画出各指标曲线。想判断瓶颈是不是CPU就看CPU曲线是否随着并发上升打到90%以上想判断是不是磁盘慢就看Disks I/O曲线是否出现持续高位。这种维度的交叉对比是定位性能瓶颈的关键手段。需要注意一点ServerAgent本身也会消耗一点点被压服务器的资源控制好监控频率和指标数量就能把影响降到最小。另外生产或测试环境的防火墙要放行4444端口否则Jmeter这边会一直显示连接不上。4.4 指标怎么解读CPU、内存、磁盘、网络的合理区间拿到一堆监控数据之后还是得知道多少算正常、多少算危险。我整理了一个可以参考的表格指标健康区间关注点CPU使用率60%以下较健康70%~80%需要警惕持续90%以上要介入us高查应用代码sy高查内核与系统调用wa高查IO内存使用率全量占用70%以下较安全available不要持续低于512MB关注Swap使用率Swap持续增长说明物理内存不足磁盘IO利用率util低于70%较正常接近100%是明显瓶颈await过高说明排队严重结合队列长度看网络带宽占用低于网卡带宽的60%较正常超过80%要关注网卡软中断会伴随CPU si升高这里特别提醒一下CPU使用率并不是越低越好。如果压测已经打上去了CPU却长期趴在5%以下说明请求大概率阻塞在别的地方——可能是数据库锁等待、网络延迟或者连接池排队。这时候看服务端的线程栈和网络监控比继续加并发更有价值。5. 完整压测案例一个订单查询接口的渐进式压测5.1 案例背景与测试环境现在把前面的理论串起来完整走一遍实际案例。背景是一个订单查询接口目标值是满足峰值场景下的TPS 2000响应时间P95小于500ms错误率小于0.1%。测试环境是三台机器一台压测机运行Jmeter一台应用服务器4核8G部署了Tomcat应用和订单查询接口一台数据库服务器MySQL 8.0。应用服务和数据库分机部署避免资源互相干扰。压测机和应用服务器之间走专线网络带宽千兆排除网络带宽瓶颈的干扰。测试数据提前准备了20万条订单数据覆盖各种订单状态通过CSV文件参数化。Token用固定测试Token因为这次只压查询接口不压登录链路。5.2 渐进式加压过程与观察点第一轮50并发持续5分钟。执行命令jmeter -n -t order_query_50.jmx -l result_50.jtl -e -o report_5050并发跑下来聚合报告显示TPS大约460P95响应时间80ms错误率0。同时看PerfMon监控曲线应用服务器CPU使用率在25%左右内存稳定磁盘IO几乎为零。这个结果说明系统在50并发下非常轻松可以继续加压。第二轮把线程数加到100Ramp-Up设为10秒持续5分钟。TPS来到了900左右P95响应时间100ms错误率仍然为0。服务端CPU上升到45%依然有比较大的余量。第三轮线程数加到200。这一轮开始出现明显变化TPS到了1600之后就不再线性增长了P95响应时间飙升到800ms错误率0.5%。查看监控曲线应用服务器CPU已经跑到95%以上内存、磁盘、网络都还有余量。到这里可以基本定位瓶颈在应用服务器的CPU而不是下游数据库或网络。通过这个渐进过程我们可以得出一个初步结论单机4核8G的应用服务器这个订单查询接口的实际处理上限大约在1600~1700 TPS。要达到2000 TPS的目标要么扩容应用服务实例要么优化接口的计算逻辑降低CPU消耗。接下来就拿着这份数据去和开发沟通方向非常明确。5.3 生成HTML报告并输出压测结论压测结束后Jmeter命令行模式会自动生成一个HTML报告打开report_50目录下的index.html就能看到完整的压测结果包括吞吐量曲线、响应时间分布、错误率统计等信息。写压测报告时我的习惯是把下面几个信息全部带上测试时间、压测脚本版本、被测服务版本压测机和应用服务器配置、网络情况各档位并发的TPS、响应时间P50/P95/P99、错误率服务端CPU、内存、磁盘、网络的监控曲线截图瓶颈定位结论和推荐优化方案如果优化后要复测比如开发把接口查询逻辑做了优化就用同一个脚本、同一种数据、同样的并发档位再压一轮和上次的数据做对比。只有控制变量压测结论才有参考价值。6. 常见问题与避坑实录6.1 压测常见问题速查表我把这几年做接口压力测试遇到的高频问题整理成了一张表遇到对应现象可以直接查现象可能原因解决办法Jmeter启动闪退或无反应JAVA_HOME未配置、JDK位数不匹配检查环境变量命令行运行jmeter.bat看报错信息压测报“Address already in use”Windows端口号耗尽命令行用netsh命令调整动态端口范围或开启连接复用Linux上报“Too many open files”文件句柄数限制过高并发下不够用调大ulimit修改limits.conf压测机自身CPU打满GUI模式开监听器消耗资源改用命令行模式必要时分布式压测并发上去了但TPS不涨服务端资源已达瓶颈看监控定位CPU/内存/磁盘/网络谁先打到上限错误率高且响应时间剧增数据库连接池耗尽、线程池排队查服务端连接池配置和线程池活跃线程数压测结果吞吐量为0采样器没有真正发送请求路径或参数配置错误先在GUI下用查看结果树确认请求成功这里也要区分一下JMeter这类接口压测工具和专门的CPU压力测试工具。像R23Cinebench R23这类软件是单独压CPU计算能力的用来测硬件散热和稳定性而Jmeter压的是业务接口目标是看整个服务链路在流量冲击下的表现。很多人一搜“压力测试”就把这两个概念混在一起其实它们应用场景完全不同。如果只是想验证电脑CPU是不是稳定用R23跑一轮就行如果想知道线上接口能扛多少并发那才用到Jmeter。6.2 服务端CPU异常占用排查思路压测过程中偶尔会遇到一种情况明明并发还没有加多少服务端CPU却已经飙上去了甚至压测结束了CPU还下不来。面对异常CPU占用第一步是定位是哪个进程在消耗CPU。Linux下用top看进程找到CPU占用最高的PID之后再用top -Hp PID查看这个进程内哪个线程在跑如果需要更详细的信息可以抓线程快照分析jstack PID thread_dump.txt看一下线程栈基本就能区分是业务代码在死循环、GC线程在频繁回收还是有线程在持续占用CPU。排查过程中还要留意一种特殊情况CPU占用高不一定是压测流量导致的。比如Windows环境下我遇到过“服务主机DCOM占用CPU高”的现象任务管理器里服务主机进程CPU占用居高不下但被压的Java进程CPU反而正常。这种情况通常和COM组件、计划任务或者某些后台服务频繁启动有关常见处理思路是更新系统组件、关闭无关的计划任务、在服务管理里停掉不需要的后台服务。所以我做压测前有一个习惯先花两分钟看一眼被压服务器有没有奇怪的进程在跑避免把别的问题算到压测头上。如果你还想看CPU温度Linux下可以装lm-sensors执行sensors命令直接读取每个CPU核心的温度。温度信息在长时间稳定性压测里很有用特别是夏天机房温度高的时候CPU过热降频会导致压测数据出现异常波动。6.3 压测过程中的几条实在心得最后分享几个我在实际项目里沉淀下来的习惯。第一压测环境和生产环境差距越大压测结论的参考价值就越低。条件允许的话尽量在和生产同等配置的环境上压或者直接把压测放到生产集群的某一个独立节点上用集群流量隔离的方式来做。第二压测脚本本身也需要被审查。我以前犯过一个错误脚本里把请求参数写死导致所有请求都命中了Redis缓存接口的P95响应时间只有20ms整个团队都很兴奋。后来把参数随机化重新压P95直接涨到300ms。从此之后每次压测前我都会检查一遍脚本里的参数化配置确认数据是分散的。第三连续压测时间不要太短。很多人做压测只压3分钟看着没问题就收工。3分钟暴露不了JVM堆内存的缓慢增长也暴露不了数据库连接池随着时间慢慢耗尽。起码跑到20~30分钟长稳测试对线上系统才更有参考意义。在做压测项目的这些年里我最深刻的体会是压测报告里最有说服力的不是聚合报告上那一排数字而是“当并发加到某个值时CPU、内存、磁盘、网络这四类指标中哪一个先到达了极限”。只要把这个先后顺序搞清楚了系统的容量边界和优化方向就都浮出水面了。做压力测试本质上是在给系统找上限但找上限的同时也是在为稳定性画一条安全线。