ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JMeter 性能测试教程:安装配置、接口压测与常见报错排查

JMeter 性能测试教程:安装配置、接口压测与常见报错排查 性能测试这个活儿干过的人都知道最怕的不是压不上去而是压上去了却不知道数据从哪来。JMeter 是我这些年用得最顺手的一款开源压测工具纯 Java 写的跨平台装完就能跑接口测试和性能压测都能扛。这次把 JMeter 安装配置使用的完整流程重新捋一遍从 JDK 环境准备、下载安装包、目录结构解读到接口测试、参数化、Beanshell 断言、脚本录制、HTTPS 证书处理再到命令行压测和常见报错排查全部按我实际操作的顺序写出来。不管你是刚接触性能测试的新手还是想把这套工具用得更扎实的老手照着走一遍基本就能上手干活。里面踩过的坑、绕过的弯我都会标出来省得你重复走。1. 环境准备与 JDK 安装配置JMeter 是跑在 Java 虚拟机上的没有 JDK 它连启动脚本都执行不了。这一步是整个链条的地基配置错了后面全是莫名其妙的报错。我见过太多人卡在“明明装好了 JMeter双击却没反应”这种情况九成是 JDK 环境变量没配对。1.1 版本选择别追最新追最稳先说版本。JMeter 5.x 系列对 JDK 的要求最低是 Java 8官方推荐 Java 8 或 Java 11。我个人的习惯是JDK 用 8 或者 11JMeter 用 5.4.1 或 5.6.3。为什么不建议直接上最新的 JDK 17、21因为部分第三方插件比如 MQTT 插件、某些自定义函数库编译时用的是老版本字节码跑在高版本 JDK 上会抛UnsupportedClassVersionError。这个错误信息看起来很长很吓人本质上就是“你用的 Java 版本比这个 jar 包编译时的版本高太多”。所以选型逻辑很清晰如果你只是做常规 HTTP 接口和压测JDK 8 足够如果项目本身用了高版本 Java那就 JDK 11再往上就要先测插件兼容性。JDK 和 JMeter 的对应关系可以看下面这张表。JMeter 版本推荐 JDK说明5.4.1JDK 8 / 11兼容性最好插件生态最全5.5JDK 8 / 11修复部分监听器性能问题5.6.3JDK 8 / 11 / 17新版本部分老插件需验证6.xJDK 17架构调整老脚本迁移需测试1.2 JDK 安装与环境变量配置下载 JDK 我一般走官方归档页或者国内的镜像源选择对应系统的安装包。Windows 下就是一路下一步关键是安装完之后的环境变量。很多人装完只配了JAVA_HOME结果命令行敲java -version还是提示找不到命令问题出在没配Path。具体三步新建系统变量JAVA_HOME值填 JDK 的安装根目录比如C:\Program Files\Java\jdk1.8.0_361。注意不要带\bin这个目录指向的是 JDK 根不是 bin。编辑系统变量Path新增一条%JAVA_HOME%\bin。这样系统在任何路径下都能找到 java 命令。可选但推荐新建CLASSPATH值填.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar。现在很多场景不需要它了但配上不亏。配完关掉所有命令行窗口重新开一个敲java -version和javac -version两个都能输出版本号才算成功。这里有个坑如果系统里之前装过其他版本的 JavaPath里可能有多条 java 路径系统按顺序取第一个。你可以在命令行敲where java看看实际调用的是哪一个顺序不对就把 JMeter 需要的那个往上挪。Linux 和 Mac 下更简单解压后编辑~/.bash_profile或~/.zshrc加上export JAVA_HOME/usr/local/jdk1.8.0_361 export PATH$JAVA_HOME/bin:$PATH保存后执行source ~/.bash_profile让它生效。实测下来把$JAVA_HOME/bin放在PATH最前面是个好习惯能避免被系统自带的旧 Java 抢占。注意环境变量改完一定要新开终端验证光在当前窗口source有时候不彻底尤其是 Windows 下改Path必须重启命令行。2. JMeter 下载安装与目录结构解析环境搞定接下来是 JMeter 本体。这工具是绿色免安装的解压即用但“解压即用”不代表“随便放哪都行”目录位置和结构理解不到位后面配插件、改参数会找不到北。2.1 下载渠道与版本确认下载只认准一个地方Apache 官网的 JMeter 下载页找到Binaries那一栏下载apache-jmeter-x.x.x.zipWindows或.tgzLinux/Mac。不要从各种第三方站点的“高速下载”拿那些包经常被塞了修改过的配置或者残缺文件跑起来出问题你根本查不到原因。下载完解压到一个路径里没有中文、没有空格的目录比如D:\tools\apache-jmeter-5.6.3。为什么强调这个JMeter 的启动脚本和部分插件在处理中文路径时会有编码问题报错通常是Could not find or load main class看起来像环境问题其实是路径惹的祸。这个坑我在一个客户现场蹲了半小时才反应过来。2.2 目录结构逐层拆解解压后根目录下这几个文件夹和文件每个都有用一一说明bin核心目录。jmeter.bat是 Windows 启动脚本jmeter.sh是 Linux/Mac 脚本。jmeter.properties是主配置文件改语言、改日志级别、改 SSL 都在这。user.properties是用户自定义配置升级时不会被覆盖我建议所有个性化配置都写这里。jmeter-server用于分布式压测。lib放依赖 jar 包JMeter 启动时会加载这里的 jar。lib/ext插件专门放这里。你下载的 MQTT 插件、各类第三方取样器jar 包丢进这个目录重启 JMeter 才能生效。docs官方文档和 API 说明想写自定义插件可以翻。extras一些辅助工具比如jmeter-ant相关的构建脚本。printable_docs可打印的文档用得少。记住一条普通依赖放lib插件放lib/ext放错位置插件加载不了界面上根本看不到对应元件。2.3 中文界面与基础性能配置启动 JMeter 后界面默认是英文。切中文有两条路菜单栏Options-Choose Language-Chinese (Simplified)。这个方式只对当前会话有效重启就没了。永久生效编辑bin/jmeter.properties找到language这一行改成languagezh_CN取消前面的注释符号。改完重启界面就是中文了。我一般直接用第二种省得每次切。顺手把几个性能相关配置改了。在jmeter.properties里找到这些# 结果文件保存格式改成 CSV 更省空间 jmeter.save.saveservice.output_formatcsv # 关闭界面上的监听器自动刷新减少压测时的资源占用 jmeter.save.saveservice.autoflushfalse再打开bin/jmeter.bat找到设置堆内存的那一行set HEAP-Xms1g -Xmx1g -XX:MaxMetaspaceSize256m默认是 1g如果你的机器内存够、压测并发大可以调到 2g 或 4g。这个值不是越大越好因为你是在本机跑压测JMeter 自己也要消耗 CPU 和内存开太大反而拖累被测系统。我的经验是单机压测堆内存给到物理内存的 1/4 到 1/3 比较合适。提示改jmeter.bat是全局改动团队里如果有人不希望你动就把内存参数写进user.properties或者用命令行-J参数传灵活一些。3. 核心元件与测试计划设计思路界面装好了真正干活靠的是元件。JMeter 里的元件种类不少但日常用到的就那么几个。理清它们的关系和作用域是写出靠谱脚本的前提。很多人脚本跑不通不是参数写错而是元件挂错了位置。3.1 八大元件类型与执行顺序JMeter 的测试计划是一棵树树上挂的元件按类型分工。核心八类线程组定义并发用户数、循环次数、启动时间是所有取样器的容器。取样器真正发请求的元件HTTP 请求、JDBC 请求、MQTT 请求都属于它。逻辑控制器控制请求的执行逻辑比如循环、条件判断、事务。配置元件给请求提供默认值和环境信息如 HTTP 请求默认值、CSV 数据集配置。定时器控制请求之间的等待时间模拟真实用户操作节奏。前置处理器请求发出前执行如参数预处理。后置处理器请求返回后执行如正则提取、JSON 提取用于关联。断言校验响应结果是否符合预期。监听器收集并展示结果如查看结果树、聚合报告。执行顺序是固定的配置元件 - 前置处理器 - 定时器 - 取样器 - 后置处理器 - 断言 - 监听器。这个顺序要背下来因为它决定了你的参数在哪一步生效。3.2 作用域规则树形结构的就近原则作用域是这个工具最容易踩坑的地方。规则是元件对它所在节点及其所有子节点生效。换句话说一个 HTTP 信息头管理器放在线程组下面线程组里所有请求都会带上这个头如果只想让某一个请求带就把它挂在那个请求下面。作用域的优先级还遵循“就近覆盖”越靠近取样器的配置元件优先级越高。举个例子线程组级别设置了Host: api.test.com某个请求下面又单独设了 HTTP 请求默认值Host: api.other.com那这个请求最终走的是后者。关联操作里的提取器也是同理。正则表达式提取器挂在哪个请求下就只提取那个请求的响应。我之前帮人排查一个“变量取不到值”的问题找半天发现是提取器挂错了层级挂到了线程组上结果它去提取了线程组里最后一个请求的响应自然拿不到想要的内容。注意监听器不要乱加。每加一个监听器JMeter 就多一份内存和磁盘开销。压测阶段界面上只留“聚合报告”就够查看结果树这种重量级的只在调试接口时开。4. 接口测试实操从 HTTP 请求到参数化环境、元件都清楚了进实操。先拿一个普通的 HTTP 接口练手跑通之后再说参数化、断言这些进阶内容。这部分我按“新建请求 - 加配置 - 关联 - 断言”的顺序走。4.1 第一个 HTTP 请求新建测试计划 - 右键添加线程组 - 线程组下添加取样器HTTP 请求。填几项关键内容协议http 或 https。服务器名称或 IP接口域名不要带http://前缀。端口号80 或 443不填走默认。方法GET、POST、PUT、DELETE 按接口文档来。路径接口的具体路径比如/api/v1/user/login。对于 POST 提交 JSON 的接口切到消息体数据标签页把 JSON 贴进去并且一定要加一个 HTTP 信息头管理器设置Content-Type: application/json。不加这个头后端经常直接返回 415 或者解析失败很多人以为是脚本问题其实是少了头。参数怎么写分场景查询参数直接填在参数表格里JMeter 自动拼到 URL 后面。RESTful 路径参数比如/user/{id}直接把 id 拼在路径里如/user/1001。表单提交在参数里填键值对勾选或不勾选编码按需。JSON 体走消息体数据。文件上传切到文件上传标签页填文件路径和参数名同时接口的方法要选 POST且勾选对 POST 使用 multipart/form-data。4.2 参数化让数据动起来写死的脚本只能跑一次真实压测需要不同用户、不同数据。JMeter 的参数化方式有几种我按使用频率排第一种CSV 数据集配置。线程组下加配置元件 - CSV Data Set Config指定文件路径、变量名逗号分隔、分隔符、是否循环读取。文件里一行就是一组数据JMeter 按行读配合线程数实现“每个用户取不同行”。username,password user001,pass001 user002,pass002 user003,pass003配置里变量名称填username,password请求参数里用${username}和${password}引用。关键参数解读Recycle on EOF文件读完是否重头再来压测通常选 true。Stop thread on EOF读完是否停止线程一般 false。Sharing mode多线程共享模式All threads表示所有线程共用一个文件读取指针。第二种函数生成。JMeter 内置了很多函数比如${__Random(1,1000,)}生成随机数${__UUID()}生成唯一 ID${__time(,)}取当前时间戳${__counter(TRUE,)}自增计数。这些在构造唯一订单号、用户 ID 时特别顺手。第三种JDBC 参数化。这个稍微复杂但非常实用。需要先加JDBC Connection Configuration配置数据库连接池再通过JDBC Request查询数据把结果存到变量里供后续请求引用。配置连接池要填的项数据库 URL如jdbc:mysql://127.0.0.1:3306/testdb、驱动类com.mysql.jdbc.Driver或com.mysql.cj.jdbc.Driver、用户名、密码以及一个连接池变量名比如mysqlPool后面 JDBC Request 靠这个名字找连接。JDBC Request 里的 Query Type 要选对Select Statement查询结果可存入变量。Update Statement增删改。Prepared Select Statement带占位符的查询防注入。查询语句比如select id from users limit 10然后在Variable names里填uid后续用${uid_1}、${uid_2}这样按行取。注意结果集变量是变量名_行号的格式行号从 1 开始。这个格式记不住的话可以先加一个Debug Sampler加查看结果树运行后看看变量实际叫啥名。提示JDBC 驱动 jar 包mysql-connector-java-x.x.x.jar要放到lib目录下不是lib/ext放错了会报ClassNotFoundException。4.3 断言判断结果对不对请求能发出去不代表结果对。断言就是自动判断响应是否符合预期。常用两种响应断言。加断言 - 响应断言配置项里测试字段响应文本、响应代码、响应信息、响应头等。模式匹配规则包含、匹配、相等、子字符串。要测试的模式写期望的内容。比如校验接口返回包含code:200就选“响应文本”“包含”模式填code:200。想校验状态码就选“响应代码”“相等”模式填200。Beanshell 断言。当校验逻辑比较复杂比如“返回的 id 必须大于 0 且用户名长度小于 20”响应断言搞不定就用 Beanshell 断言。它本质是一段 Java 代码能访问 JMeter 内置对象// 获取响应内容 String response prev.getResponseDataAsString(); // 获取响应码 String code prev.getResponseCode(); if (!200.equals(code)) { Failure true; FailureMessage 响应码不是200实际为 code; } // 用 JSON 解析校验字段 // 需要引入 json 库或用正则简单判断 if (!response.contains(\status\:\success\)) { Failure true; FailureMessage 业务状态不是 success; }Beanshell 断言里的核心变量变量含义prev上一个取样器的结果对象vars当前线程的变量可读写props全局属性跨线程可读ctx上下文对象Failure设为 true 表示断言失败FailureMessage失败时输出的信息注意Beanshell 性能较差压测高并发时慎用能换成 JSR223 断言Groovy就换Groovy 执行速度比 Beanshell 快很多语法也兼容。4.4 关联把上一个请求的结果传给下一个接口链式调用是常态比如登录拿 token后续请求都要带这个 token。这就需要关联。加后置处理器 - 正则表达式提取器引用名称变量名如token。正则表达式token:(.?)。模板$1$。匹配数字0 表示随机取一个1 表示取第一个。提取出来后在后续请求的 HTTP 信息头管理器里加Authorization: Bearer ${token}就能用了。JSON 响应我更推荐用JSON 提取器写 JSONPath 表达式比如$.data.token比正则直观也不容易写错。XPath 提取器用于 XML/HTML 响应。关于 Cookie如果接口依赖会话加一个配置元件 - HTTP Cookie 管理器就行它会自动管理 Cookie模拟浏览器的行为。配合关联一起用基本能覆盖大部分登录态场景。5. 脚本录制与 HTTPS 证书处理手工写请求适合接口少的情况。接口一多一个个手搓太慢用录制功能效率翻倍。但 HTTPS 网站录制绕不开证书这个坎这里说清楚。5.1 代理录制方式JMeter 自带录制功能原理是它自己当代理服务器浏览器走它的代理所有请求都被它抓下来变成脚本。步骤测试计划下加非测试元件 - HTTP(S) Test Script Recorder。设置端口默认 8888被占用就换一个。设置目标控制器指定录制的请求存到哪个线程组。点Start启动录制。浏览器配置代理地址填127.0.0.1端口填 8888。录完之后把代理关掉脚本就到手了。录制过程中浏览器只能访问被测站点其他网站的请求也会被录进去录完记得清理无关请求。5.2 HTTPS 证书配置录制 HTTPS 站点浏览器会警告证书不安全。解决方法启动录制时JMeter 会在bin目录下生成一个证书文件ApacheJMeterTemporaryRootCA.crt。把这个证书导入到系统或浏览器的受信任根证书列表里。Windows 下双击证书文件 - 安装证书 - 本地计算机 - 将所有的证书都放入下列存储 - 受信任的根证书颁发机构 - 完成。导入后重启浏览器再走代理录制就不会报警告了。针对JMeter 自己发 HTTPS 请求时的证书校验有两种处理信任所有证书不推荐用于生产环境在jmeter.properties里设置https.default.protocolTLS并配置信任库或者直接在 HTTP 请求里勾选相关选项。正规做法把服务端的证书导入到 JMeter 的信任库bin/truststore.jks里。我实测下来的经验是内网测试环境图方便可以让 JMeter 忽略证书校验但对外接口或者要出正式报告的场景老老实实导证书否则数据不严谨。注意录制只是帮你把手动操作转成脚本的初始形态录出来的脚本往往有冗余、参数写死、没有断言。真正能用的脚本录完还得自己整理删多余请求、做参数化、加断言、加关联。6. 性能压测实操步骤接口调通了进压测环节。压测的核心不是“把并发调大”而是设计出贴近真实业务的场景然后拿到可信的数据。这一节说清楚参数怎么算、脚本怎么跑、报告怎么看。6.1 压测场景设计与参数计算线程组三个关键参数线程数并发用户数、Ramp-Up 时间多久把用户全部启动、循环次数。Ramp-Up 时间的计算假设要模拟 100 个用户希望 10 秒内逐步启动完那 Ramp-Up 就是 10。如果希望每秒启动 10 个用户Ramp-Up 就是 10 秒循环次数视测试时长定。不要设置 Ramp-Up 为 0那样 100 个用户瞬间全上对系统是冲击不是压测数据也不真实。持续时间的计算如果想压 10 分钟用循环次数不好控制改用线程组里的调度器设启动延迟和持续时间JMeter 会按时间控制。TPS 的估算TPS 并发数 / 平均响应时间秒。举个例子平均响应 200ms0.2 秒想达到 500 TPS理论并发数就是 500 × 0.2 100。实际因为网络、定时器、思考时间的损耗并发数要往上加一些。这个公式能帮你反推需要多少并发。定时器的作用为了让请求之间有真实用户操作间隔加一个固定定时器或高斯随机定时器。固定定时器设 1000ms每个请求之间等 1 秒更接近真人操作。压测时如果追求极限吞吐可以不加定时器模拟真实场景一定要加。6.2 命令行压测与报告生成界面上压测是禁忌。GUI 模式本身消耗大量资源官方明确说不建议用 GUI 做正式压测。正确姿势是命令行jmeter -n -t test.jmx -l result.jtl -e -o report参数含义参数作用-n非 GUI 模式运行-t指定测试脚本 .jmx 文件-l指定结果输出文件 .jtl-e压测结束后生成 HTML 报告-o指定报告输出目录目录必须为空或不存在踩坑提醒-o指定的目录如果已经存在且有文件命令会直接报错Cannot write to xxx as folder is not empty。要么换个新目录要么先清空。这个错误我第一次见的时候纳闷了半天。另外结果文件.jtl如果已存在JMeter 默认会追加写入可能混入上次的数据建议每次压测用带时间戳的新文件名比如result_20240101.jtl。压测过程中想在命令行看简单进度可以不加-l先跑或者用-J传参调整报告粒度jmeter -n -t test.jmx -l result.jtl -e -o report -Jjmeter.reportgenerator.overall_granularity1000overall_granularity控制报告里时间序列的粒度单位毫秒默认 60000一分钟一个点。压测时间短的话调小一点看图更细。压测完打开报告目录里的index.html能看到聚合数据。重点看这几个指标TPS每秒事务数越大越好是吞吐能力的直接体现。平均响应时间平均值容易被极值拉偏参考价值有限。90%/95%/99% 响应时间分别表示 90%、95%、99% 的请求在这个时间内完成比平均值靠谱得多。做性能指标一般看 90 或 95 线。错误率必须为 0 或接近 0有错误说明脚本或服务有问题数据不可信。提示做正式压测前先小并发比如 1-5 个线程跑一遍确认脚本正确然后再逐步加压。上来就大并发脚本有问题的话白跑还浪费环境。7. 常见报错与排查技巧实录工具用久了报错见得多。这一节把高频问题和排查思路整理成速查表都是我实际遇到并解决过的。7.1 典型异常速查表报错信息可能原因解决方法java.io.IOException: Error writing to server服务端主动断开连接、超时、keep-alive 策略冲突加长超时时间、关闭 keep-alive连接头设 Connection: close、检查服务端连接数限制Address already in use端口被占用常见于录制代理端口换端口或杀掉占用进程UnsupportedClassVersionErrorJDK 版本与 jar 包编译版本不匹配换对应版本 JDK或更新插件ClassNotFoundException驱动 jar 没放对位置JDBC 驱动放 lib插件放 lib/extOutOfMemoryError: Java heap space堆内存不足调大 jmeter.bat 里的 HEAP 值Cannot write to xxx as folder is not empty报告目录非空清空目录或换新目录断言失败FailureMessage为空断言配置或变量引用问题用 Debug Sampler 查看变量实际值文件已经存在结果文件或录制文件重名删除或重命名旧文件7.2 关于Error writing to server的深入排查这个报错出现频率最高值得单独说。它本质是 JMeter 往服务端写数据时连接被对方关掉了。排查顺序先看服务端有没有报错日志。很多情况下是服务端处理不过来、线程池满了主动拒绝根子在服务端不在 JMeter。检查超时配置。在 HTTP 请求里设置连接超时和响应超时比如都设 30000ms。默认无限制网络慢的时候容易卡死。关闭 keep-alive。在 HTTP 请求或 HTTP 请求默认值里把Use KeepAlive取消勾选或者在信息头管理器里加Connection: close。有些服务端 keep-alive 时间短JMeter 复用了已经失效的连接就会报这个错。看并发是否过大。小并发正常大并发才报说明是压测压力超出了服务端承载这时候要看的是性能瓶颈不是脚本问题。7.3 防伪标记类问题的处理测试一些基于特定框架的 Web 项目时可能遇到提示缺少防伪标记比如某些 MVC 框架的 CSRF token 校验。这不是 JMeter 的问题是服务端的安全机制在起作用。处理思路是把 token 当成一个需要关联的参数先请求获取 token 的页面用正则或 XPath 提取器把 token 值抓出来。在后续提交请求里带上这个 token 参数。排查时可以开查看结果树看请求实际发出去的参数里有没有 token、值对不对。很多“未提供必要标记”的报错本质就是提取器没抓到值或者变量名写错。7.4 我个人踩过的几个坑第一个坑变量作用域和命名冲突。我用${token}当变量名结果多个线程组都用了同名变量互相覆盖偶发失败。后来统一加前缀比如login_token、api_token问题消失。变量命名尽量带上业务前缀。第二个坑Beanshell 性能。早期我把复杂校验全写成 Beanshell 断言小并发没事压到几百并发JMeter 自己 CPU 跑满TPS 上不去。换成 JSR223 Groovy 后同样并发下 JMeter 资源占用降了一大截。压测脚本里能用内置元件解决的就别写脚本非要写就用 Groovy。第三个坑监听器拖后腿。界面上开了“查看结果树”压测时每一个请求的完整报文都存内存、写盘几百并发直接卡死。后来学乖了压测阶段只留聚合报告需要看明细就单独跑小并发。第四个坑MQTT 等插件安装。做物联网压测需要 MQTT 插件下载对应的 jar 包后放lib/ext重启 JMeter。注意有的插件有依赖包得一起放进去不然启动时静默失败界面上看不到元件。可以看bin/jmeter.log日志加载失败会在里面留痕。第五个坑数据库连接池释放。JDBC 连接如果不释放跑久了连接数耗尽报Too many connections。JMeter 本身会管理连接池但在分布式压测时要确认每个节点都配了连接信息。7.5 压测数据可信度的自检清单拿到数据先别急着发报告自己过一遍这几条错误率是不是 0有错误先排查原因别硬着头皮分析。脚本里有没有写死的会话、token、时间戳写死的会导致只有第一个请求成功。思考时间、定时器设置是否合理完全没间隔的压测数据偏乐观。压测机和被测服务是不是同一台机器同机会抢资源数据不准。有没有开 GUIGUI 模式下数据不作数。单次压测时长是否足够太短的数据受启动阶段影响大一般跑 5-10 分钟以上更稳。这些检查项是我带队做压测时反复强调的新手最容易忽略第 2 条和第 4 条结果数据看着漂亮一上真实环境就露馅。我个人在实际操作中的体会是JMeter 这个工具入门门槛不高但要用得准功夫都在细节上。安装配置只是起点真正拉开差距的是脚本的严谨程度和对数据的敬畏心。一个能复现、可解释、经得起追问的压测结果比一个漂亮的 TPS 数字有价值得多。后面如果还要往深里走可以研究分布式压测多台机器一起加压、持续集成里集成 JMeter 做自动化性能回归把性能测试从一次性活动变成常态化能力那时候工具还是这个工具但整个团队的质量基线会不一样。
返回列表