尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

JMeter高并发压测实战:从脚本设计到性能分析全攻略

发布时间:2026/9/29 5:06:29

资讯中心
01
ARTICLE

JMeter高并发压测实战:从脚本设计到性能分析全攻略

JMeter高并发压测实战:从脚本设计到性能分析全攻略
最近在整理一套云上环境迁移验证方案原本跑在单节点K8s里的若依微服务整套环境要在不停服、不丢数据的前提下迁到云上的ECS迁完之后还得让压测人员用配套脚本跑一轮高并发验证云上承载能力。接到这个任务的时候我心里第一个冒出来的工具就是JMeter。不是因为它功能最全而是因为它门槛低、生态成熟从接口调试到分布式压测都能覆盖而且完全是开源免费。这篇文章没有太多高深理论都是我实际用JMeter时踩过坑、查过文档、反复验证后攒下来的学习笔记从下载安装到脚本设计、从参数化到Beanshell断言、从JDBC取数到文件上传再到底层报错排查全部按真实使用顺序记录下来。如果你刚入门JMeter可以先照着前两章把工具跑起来如果已经会基本操作建议重点看后面的参数化、断言和常见报错这些才是实战中最容易卡住你的地方。1. JMeter初识与环境准备1.1 为什么选择JMeter做性能测试的工具有很多商业的有LoadRunner开源的有Gatling、k6、Locust但JMeter在“上手速度”和“覆盖范围”之间平衡得最好。它最核心的能力是模拟多用户并发请求但它同时也能做接口回归、数据库压力测试、消息队列测试甚至还能通过插件支持MQTT、WebSocket这些协议。我们这次迁移后的压测主要针对若依微服务网关和几个核心业务接口JMeter的HTTP代理录制和线程组模型正好够用。另一个很现实的理由是团队协作成本。JMeter脚本本质是XML文件虽然直接改XML不友好但通过GUI调整参数、保存脚本团队成员之间可以很快复用。配合CSV文件做数据驱动压测人员不需要写代码也能改并发数、循环次数和数据源。这种“低代码”特性在项目交付现场特别重要因为压测人员往往不是开发出身你给他一套能改参数的脚本比让他去理解代码逻辑要高效得多。1.2 下载、安装与目录结构JMeter是纯Java应用前提是机器上要有JDK。我这里用的是JDK 8和JMeter 5.6稳定且兼容性最好。下载直接搜“JMeter官网下载”进入到Apache JMeter的下载页面选择对应的zip包。Windows下解压后能看到这些关键目录bin启动脚本、日志配置、核心可执行文件lib基础依赖库JDBC驱动、插件包都放这里lib/ext扩展插件目录比如MQTT插件、自定义函数放这里docs离线文档和API说明配置环境变量时我习惯把JMETER_HOME配到解压根目录这样后续写Beanshell或者引用外部依赖时能少踩路径坑。Windows下直接双击bin目录里的jmeter.bat就能打开GUILinux服务器上一般用jmeter -n -t脚本.jmx -l result.jtl这种命令行模式跑回归压测避免GUI消耗资源。有一点要特别提醒不要用系统自带的旧版本Java去跑最新版JMeter。JMeter 5.6需要Java 8以上但Java 16在部分组件比如某些插件上会有兼容问题。如果你不需要新特性老老实实装个Java 8最省心。1.3 简单压测步骤第一次用JMeter做压测不要一上来就搞复杂场景。先跑通最简单的流程打开JMeter添加线程组添加HTTP请求采样器添加聚合报告监听器然后直接点运行。线程组里的几个核心参数要理解线程数模拟多少个并发用户。注意JMeter的线程是“会话”级别的一个线程在跑完整个循环之前会一直占用资源。Ramp-Up时间线程启动耗时。如果把Ramp-Up设为0所有线程会瞬间启动对服务器冲击很大设为合理值可以模拟用户逐渐进入系统的真实情况。循环次数每个线程执行脚本的次数。勾选“永远”则一直跑直到手动停止。启动时建议先跑少量线程做冒烟比如5个线程、循环1次确认接口返回都是200再把线程数拉高。很多人一上来就1000并发结果脚本本身有错最后压测数据没法看白费时间。2. 脚本设计与性能测试步骤2.1 测试计划与线程组设计一个标准的JMeter测试计划通常包含测试计划节点、线程组、配置元件如HTTP请求默认值、HTTP头管理器、JDBC Connection Configuration、采样器HTTP请求、JDBC Request、断言响应断言、Beanshell断言、监听器聚合报告、查看结果树。我先说线程组设计。对于若依这种带认证的微服务系统压测场景一般分三类单接口压测一个线程组里只放一个HTTP请求主要是测某个接口的吞吐量和响应时间。全链路压测一个线程组里按业务顺序放多个请求比如登录、查询列表、提交订单需要处理好Token传递。混合场景压测多个线程组同时跑通过设置不同线程数模拟不同业务比例的流量。设计时最好把“登录”和“业务操作”拆开。因为登录接口走的是安全认证逻辑并发太高会受到验证码、密码错误锁定等限制影响业务接口的真实指标。我们这次压测是先用预备脚本批量生成Token然后通过JMeter的CSV文件读取Token这样压测时只跑业务接口。2.2 HTTP请求与参数编写HTTP请求采样器是最常用的元件。协议、服务器名称或IP、端口、方法、路径、请求体这些字段要小心填写。最容易出错的地方有两个一是“服务器名称或IP”和“路径”不要混在一起。有人习惯直接填http://192.168.1.10:8080/api/login但JMeter要求协议单独填http服务器填192.168.1.10端口填8080路径填/api/login。混填之后JMeter会报错找不到主机其实不是网络问题是格式问题。二是RESTful接口的参数怎么填。现在的微服务接口大多是RESTful风格路径里带了变量比如/user/{id}/order/{orderId}。这时候不要在“参数”页签里写{id}应该直接在路径上写成/user/1001/order/8899。如果id需要动态变化可以通过${变量名}引用JMeter变量比如/user/${userId}/order/${orderId}。需要传查询参数时可以放在“Parameters”里也可以直接拼在URL里两种方式等价但注意中文字符要做URL编码。如果接口要求JSON请求体在“Body Data”里填写原始JSON字符串。一定要用JMeter的${变量名}做动态替换别写死。比如登录接口{ username: ${loginName}, password: ${loginPassword}, code: ${captchaCode} }这里变量可以来自CSV文件、用户定义的变量也可以来自上一个请求的JSON提取器。2.3 Cookie、Header与CSRF Token处理很多业务系统接口需要维持会话。若依这类框架虽然走JWT但有些接口也会校验Cookie里的SessionId。JMeter里处理Cookie有两种方式一是用HTTP Cookie管理器让它自动管理Cookie二是手动在HTTP头管理器里加Cookie头。我建议优先使用HTTP Cookie管理器因为它会自动存储服务器返回的Set-Cookie并在后续请求中自动带上。实际压测中发现若依后台的某些POST接口会校验__RequestVerificationToken这种防伪标记。这个Token一般藏在页面里或者由登录接口返回如果直接压测会报“未提供必要的防伪标记”。对这种接口处理思路是先发一个GET请求拿到页面里的Token用正则表达式提取出来再在POST请求里把它作为参数或Header传回去。本质上就是个动态参数提取问题。Header管理器的作用是统一添加公共Header比如Content-Type: application/json、Authorization: Bearer ${token}。注意Head里不要手动填Cookie否则会和Cookie管理器冲突导致重复Cookie。2.4 安全证书问题HTTP协议压测不会遇到证书问题但HTTPS接口需要处理。JMeter官方要求安装安全证书才能录制HTTPS脚本但只做压测的话可以通过修改JMeter配置来忽略证书验证。我常用的方式是在JMeter的bin目录下jmeter.properties文件里设置server.rmi.ssl.disabletrue但更直接的方式是使用HTTP请求采样器在“高级”选项卡里勾选Use KeepAlive并把实现选为HttpClient4同时给JVM设置信任所有证书。还有一种更省事的方法用-Djavax.net.ssl.trustStore...指定一个信任库。不过对大多数压测场景我推荐在JMeter启动时加上两个参数jmeter -Djavax.net.ssl.trustStorePasswordchangeit -Djavax.net.ssl.trustStore...如果不方便改启动参数直接用系统属性-Djsse.enableSNIExtensionfalse在某些情况下也能绕开SSL握手问题但这个方案在新版本JDK里不太稳定能不用尽量不用。3. 进阶技巧参数化、断言、上传文件3.1 JDBC Request参数化与跨请求传参微服务压测最常遇到“上一个接口返回的数据作为下一个接口参数”的需求。比如先查询用户列表拿到第一个用户的ID然后去查询这个用户的订单。JMeter里做这个事的标准组合是JDBC Connection Configuration JDBC Request BeanShell/正则提取。先配置JDBC连接。在测试计划里添加JDBC Connection Configuration填好数据库URL、JDBC驱动类、用户名和密码。比如MySQLDatabase URL:jdbc:mysql://localhost:3306/ry?useUnicodetruecharacterEncodingutf8JDBC Driver class:com.mysql.jdbc.DriverUsername/Password: 对应账号JDBC Request里写SQL时可以用WHERE id ${userId}这种占位符变量可以来自上一个请求提取的${userId}。JDBC Request的Result Variable Name设置为userList然后在后续的BeanShell断言或者JMeter自带的${userList_1}方式可以访问结果集。这里有一个坑如果你在“Variable Names”里填了多个变量名需要用下划线加序号访问比如userList_1是第一行第一列。但实际上更稳妥的方式是用BeanShell后置处理器去遍历结果集。我在做若依系统的用户-订单链路压测时是在JDBC Request后面加了“正则表达式提取器”从SQL结果中提取某个字段存入变量再在下一个HTTP请求的路径上引用。这样做的好处是逻辑简单不依赖BeanShell的API小白也能维护。3.2 CSV参数化与用户数据压测不能所有用户都用同一份数据否则你的压测其实是在压一个被缓存的结果。CSV参数化是JMeter最常用的数据驱动方式。添加CSV Data Set Config配置好文件名、变量名称、分隔符。要注意几个参数Allow quoted data如果CSV里有逗号包裹的字段要设为TrueRecycle on EOF文件读完后是否循环。如果要压测长时间稳定运行通常设为True但要注意数据可能会重复Sharing mode默认是All threads所有线程共用一个游标如果希望每个线程读取不同行可以选择Current thread我压测下单接口时CSV里放了10000个手机号每行代表一个独立用户线程数设置1000循环10次这样每个用户用的是不同手机号。如果压测场景要求同一用户多次下单那就要设置Recycle on EOF为False避免重复用户造成业务冲突。3.3 Beanshell断言JMeter自带响应断言可以判断响应是否包含某段文字。但它对复杂逻辑无能为力比如“响应码是200且JSON里某个字段大于100才算通过”。这时候就要用Beanshell断言。Beanshell里常用几个内置变量prevSampleResult对象可以拿到响应码、响应体、请求时间ResponseData响应数据字节数组varsJMeter变量工具可以读写变量log日志输出对象Failure和FailureMessage设置断言结果的关键变量比如判断若依接口返回里的code是否为200String response prev.getResponseDataAsString(); if (response.contains(\code\:200)) { Failure false; } else { Failure true; FailureMessage 响应中没有code200返回内容为 response; }注意Beanshell里引号转义容易出错。老版本的JMeter里Beanshell使用org.apache.jmeter.services.FileServer可以读取文件但新版建议尽量少用Beanshell因为性能开销大。如果只是简单断言优先用JSON提取器响应断言。如果必须用Beanshell也不要在高并发里大量使用否则会拖垮压测机。3.4 文件上传压测接口涉及文件上传时JMeter里要做几步配置。HTTP请求选择POST方法在“Files Upload”选项卡里配置文件名本地文件路径、参数名、MIME类型。比如若依的文件上传接口参数名通常是fileMIME类型按文件类型填image/jpeg或者multipart/form-data。如果你压测的是“图片上传并生成缩略图”这类接口需要在同一个请求里同时传文件和数据字段这时要在Parameters里添加业务字段同时Files Upload里添加文件。一个容易踩的坑是JMeter的Header管理器里不要手动设置Content-Type: multipart/form-data; boundary...。你一旦手动设置JMeter就不会自动生成boundary服务器解析时会报错。正确的做法是让JMeter根据Files Upload自动生成multipart请求体Header管理器里最多只设置Authorization等自定义Header。如果想用变量控制上传文件路径文件名直接写${filePath}即可。之前我用CSV参数化准备了一组不同大小的图片路径用来压测上传接口在不同文件大小下的CPU和内存表现。3.5 插件安装与MQTT扩展JMeter默认只能支持HTTP、JDBC、FTP这些协议如果要压测MQTT物联网设备接入就得装MQTT插件。下载插件包后把插件jar包放到lib/ext目录重启JMeter即可。不要直接替换lib里的核心jar否则可能导致版本冲突。推荐用JMeter Plugins Manager来管理插件。先在官网下载plugins-manager.jar放到lib/ext目录重启后右键测试计划就能看到“Plugins Manager”选项。在Available Plugins里勾选需要的功能比如Custom Thread Groups、MQTT Sampler、Standard Set等。安装插件的时候要注意和JMeter版本匹配。比如MQTT插件在JMeter 5.x上没问题但如果你用的是JMeter 4.0可能装完之后插件不显示这时优先检查jar包版本。插件管理器里建议勾选“Json Path Extractor”这类常用组件。我实际项目里用JsonPath提取器的频率远高于正则提取器因为响应结构里嵌套层级深的时候JSONPath写起来更清晰。4. 实战高并发压测与结果分析4.1 性能测试流程一次标准的性能测试我的执行顺序是梳理业务场景和关键接口明确压测目标比如QPS达到5000P95响应时间小于200ms开发环境或测试环境先跑通脚本检查是否有报错逐步加压先用50并发预热再按100、200、500、1000梯度递增每个梯度跑5分钟记录聚合报告和服务器监控数据压测结束后分析瓶颈是CPU、内存、数据库连接池还是带宽瓶颈这里的关键不是“一次性跑完”而是“找到拐点”。如果100并发时响应时间正常200并发时突然飙升那这就是系统的拐点后续优化应该聚焦在这个点上的资源限制。我在迁移后的云ECS上就是按这个流程先跑100并发确认稳定后升到300再升到500最后才跑到预期的1000并发。4.2 监听器与指标解读JMeter的监听器有很多种我平时最常用的是“聚合报告”和“查看结果树”。“聚合报告”里有几个指标必须会看Samples总请求数Average平均响应时间Min/Max最小/最大响应时间Std. Dev.标准差反映响应时间波动Error %错误率Throughput吞吐量单位通常是/sec每秒请求数Received KB/sec / Sent KB/sec网络收发速率不要只盯着Average看。如果平均响应时间是200ms但Max是3000ms说明有严重的尾部延迟。这时候要看线程运行曲线和服务器监控通常是连接池满了或GC停顿引起的。另外Error%也不是越低越好要看错误类型。如果错误全是500说明服务端有问题如果全是连接超时那可能是压测机自身网络连接数被用完。“查看结果树”是用来调试脚本的正式压测时不要开。它会占用大量内存和CPU影响压测结果。压测时一般只开聚合报告和简单的后端监听器如果还需要实时监控可以用jmeter的-l参数把结果写入jtl文件压测结束后再用报告工具生成HTML报告。4.3 压测过程中的注意事项压测机上跑JMeter如果并发数很高JMeter自身也可能成为瓶颈。一个常见的坑是JMeter默认的堆内存太小。在jmeter.bat或jmeter启动脚本里HEAP参数决定了JVM可用内存。我压测1000并发时会把HEAP-Xms2g -Xmx4g否则压测机GC频繁TPS数据完全失真。还有一点是关于分布式压测。单个JMeter客户端很难模拟几万并发这时要用JMeter Master-Slave模式多台压测机共同施压。但分布式压测有个坑master和slave都要装有相同的插件和JDBC驱动且结果集汇总时网络传输会成为瓶颈。所以做分布式之前最好先在单机上压测到饱和再决定要不要扩展。服务器端监控是另一个容易遗漏的环节。JMeter只能看到客户端视角的数据服务器CPU、内存、磁盘IO、网络带宽、数据库连接池这些信息需要通过其他方式采集。我们这次迁移到云ECS后用云监控和Prometheus同时采集能把JMeter结果和服务器资源曲线放到同一时间轴上比对。比如数据库CPU被打满JMeter里体现为响应时间飙升这时就能快速定位瓶颈是数据库还是应用。5. 常见问题与排查技巧5.1 报错java.io.IOException: Error writing to server“Error writing to server”是JMeter压测里特别常见的报错而且造成的原因不止一种。我遇到比较多的是服务端主动断开了连接或者压测机和服务端之间的Socket连接被防火墙或网络设备切断。排查思路先看服务端日志里有没有对应的断开记录再检查压测机和服务端之间的网络是否有超时策略。另一种可能是HTTP Keep-Alive引起的问题。JMeter在“HTTP请求”的“高级”页签里默认勾选了Use KeepAlive如果服务端不支持长连接或连接池里的连接已被服务端关闭就会出现“Error writing to server”。这时把Use KeepAlive取消勾选试试。如果接口本身需要登录态还要检查是不是并发后Token失效导致服务端主动断开。如果报错从压测一开始就出现且只是部分请求报错建议先在“查看结果树”里看看具体是哪个请求、哪个Sampler报错。很多时候是线程之间共享了变量比如${token}在多个线程被覆盖导致后续请求带错Token服务端拒绝连接。5.2 报错文件已经存在“文件已经存在”这个问题我最早是在做JMeter的分布式压测时遇到的。压测脚本里的CSV参数化文件、上传要用到的图片文件在分布式压测时每个Slave节点都需要有这些文件。如果你只在Master上放了文件Slave上没放JMeter会尝试从Master传输文件如果目标文件已经存在且被占用就会报“文件已经存在”。解决方案是手动把所需要的文件复制到所有Slave机器的相同目录下避免JMeter自动搬运。另外如果你是在Windows下编辑了CSV文件然后拷贝到Linux服务器上跑JMeter要小心编码问题。CSV文件里如果有中文Windows下默认可能是GBK编码Linux下JMeter读取时如果按UTF-8解析就会出现乱码进而报找不到对应数据。建议所有CSV文件统一保存为UTF-8 without BOM格式必要时在CSV Data Set Config里设置文件的编码格式为UTF-8。5.3 录制HTTPS脚本时证书和代理问题用JMeter录制HTTPS脚本时最常见的报错是SSLHandshakeException。这是因为JMeter代理服务器需要用根证书来解密HTTPS流量。解决方法在JMeter的bin目录里找到ApacheJMeterTemporaryRootCA.crt把它安装到操作系统的受信任根证书存储中。安装后还要在浏览器里设置代理代理地址填JMeter所在机器的IP端口填默认的8080。这里有个小技巧录制脚本时如果不想把系统代理设置得乱七八糟可以启动一个Firefox专用Profile只对这个Profile配置代理和证书录完直接关闭不影响日常上网。如果只是为了压测而非录制完全可以不用代理录制直接手写HTTP请求采样器从抓包工具里复制请求信息。这样虽然慢一点但至少不会被代理问题折磨。5.4 数据库连接与JDBC驱动问题JDBC Request要跑通必须把对应数据库的驱动jar包放到lib目录。比如MySQL用mysql-connector-java.jarPostgreSQL用postgresql.jarSQLServer用mssql-jdbc.jar。版本上要注意兼容性MySQL 8以上必须使用新版驱动老驱动可能会报Communications link failure或者ClassNotFoundException。连接配置里容易忽略的是JDBC Driver class常见的MySQL驱动类名有两种历史写法com.mysql.jdbc.Driver和com.mysql.cj.jdbc.Driver。如果你用的是MySQL 8的驱动必须用后者如果是老驱动用前者。配置错了会在JMeter日志里看到找不到驱动类的异常。另外数据库压测时要注意连接池大小。JMeter默认每个线程可能会创建多个连接如果数据库连接池有限就会出现连接等待超时。我在JDBC Connection Configuration里做过一次压测把Maximum Number of Connections设得比线程数小结果大量请求都卡在拿连接上看起来像数据库慢其实是连接数不够。调整方式是把最大连接数设到线程数的1.5到2倍并观察数据库端连接数变化。5.5 其他常见问题速查现象可能原因处理建议响应数据乱码页面编码和JMeter默认编码不一致在HTTP请求里设置Content Encoding为UTF-8或使用后置处理器转码聚合报告里样本数远大于预期监听器开启了所有线程的样本统计确认是否在子线程组加了监听器避免重复统计并发一高就Connection reset服务端连接数限制或压测机日志连接耗尽检查服务端文件句柄数和网络连接数调大系统参数JSON提取器没取到值用的表达式路径不对先在查看结果树里看响应结构用$.xxx.xxx格式逐步测试压测脚本在GUI运行正常命令行跑却报错工作目录不同导致相对路径失效把CSV、文件路径改成绝对路径或基于jmeter.home的相对路径Beanshell脚本不生效没有引入必要的类在脚本头部使用import引入所需类比如import org.json.JSONObject;这些问题是新手最容易遇到的但也是经验积累最快的地方。不要怕报错JMeter的日志信息非常丰富学会看日志比记十篇教程都有用。另外多说一句压测前一定要确认压测环境和目标环境的时间是否同步最好用统一的时间源。不然JMeter产生的jtl时间戳和云服务器监控的时间戳对不上分析数据的时候很难定位哪一秒服务资源开始饱和。我在迁移验证时就因为没有同步时间后面比对延迟曲线浪费了不少时间。如果条件允许压测时把JMeter的-J参数配合使用比如-JthreadNum500这样脚本里${__P(threadNum,100)}就能读取外部传入的线程数一套脚本灵活适配不同压测规模。这个技巧在团队协作时特别有用谁都不需要打开GUI改参数直接命令行传值就行。最后再提示一个问题跑完压测后如果出现大量超时错误不要只盯着JMeter的报告先看看压测机是不是自己先挂了。压测机出现文件句柄耗尽、内存不足时报告里看到的错误会全部假象。我的习惯是在压测机上同时监控JMeter进程的CPU和内存一旦发现压测机资源接近饱和就先减少线程数再继续排查服务端情况。这种方式虽然保守但能避免很多无效的加班排查。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。