ARTICLE DETAIL

资讯详情

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

JMeter性能压测实战指南:从脚本设计到分布式压测与瓶颈定位

JMeter性能压测实战指南:从脚本设计到分布式压测与瓶颈定位 在性能测试这个圈子里JMeter 的名字几乎无人不知。它是一款纯 Java 写的开源压测工具由 Apache 基金会维护用来做接口测试、性能压测、回归测试都相当顺手。很多团队的第一套压测方案就是从 JMeter 起步的它不需要写一行生产代码就能搭建出完整的测试场景而且插件生态非常丰富稍微会一点 BeanShell 和正则表达式几乎能处理所有 HTTP 层面的压测需求。这篇博客适合刚接触压测的测试工程师、后端开发以及准备搭建性能测试体系的团队参考。我会从环境准备讲到脚本编写再讲到压测过程中的坑和排查技巧把我在实际项目中踩过的坑和沉淀下来的经验都掏出来尽可能让你看完就能直接上手上路。1. 为什么选择 JMeter 作为压测工具先聊一个很多人都纠结过的问题压测工具那么多LoadRunner、Gatling、Locust 各有各的拥趸为什么我还是建议你优先考虑 JMeter这不是因为 JMeter 有多完美而是它在“够用”和“易上手”这两件事上平衡得最好。1.1 开源免费与跨平台特性带来的便捷首先JMeter 是完全开源的商用也不涉及授权费用。这一点在项目预算紧张或者团队规模较小的时候特别重要。LoadRunner 的 License 贵到让人头皮发麻而且它的 Controller 跑在 Windows 上非常顺手但到了压测机扩容的时候成本会直线上升。JMeter 则完全回避了这个痛点它本质上就是个 Java 程序只要机器上有 JDK不管你是 Windows、Linux 还是 macOS解压即用。我曾在压测现场临时找了两台 CentOS 的裸机从装 JDK 到启动压测脚本前后不到二十分钟。其次JMeter 的图形界面虽然被不少人吐槽不够现代但胜在结构清晰、层级明确新手在短时间内就能理解“线程组-取样器-监听器”这套基本模型。而且当你需要把脚本放到服务器上做非 GUI 压测时JMeter 生成的 JMX 文件本质上就是一份 XML 文档用命令行执行和用 GUI 打开的规则完全一致切换成本几乎为零。这一点两说一方面脚本可移植性强同一个 Jmx 在 Windows 上调试完直接扔到 Linux 上跑另一边也需要你在写脚本时注意相对路径和绝对路径的问题后面我会具体聊。1.2 JMeter 与同类压测工具的横向对比很多团队在做技术选型时会拿 Gatling 和 Locust 来对比 JMeter。我给个比较实际的判断依据参考下表对比维度JMeterGatlingLocust语言门槛低通过界面配置中高需要 Scala 基础中需要 Python 基础场景建模能力强线程组 控制器强场景描述式中等靠代码实现脚本可读性一般XML 冗长很好代码即文档较好Python 易读分布式压测支持成熟主从模式稳定支持但配置较繁琐支持依赖主从进程插件生态丰富JMeter Plugins 提供大量扩展主流协议偏少面向 Web 场景学习曲线平缓偏陡有一定门槛社区活跃度极高较高高如果你的核心诉求是快速落地、覆盖常用协议HTTP, HTTPS, WebService, JDBC, JMS 等JMeter 是最稳妥的选择。它不需要你额外写复杂的 DSL大多数操作都是鼠标点选加少量辅助脚本测试人员不需要依赖开发角色去帮忙写压测代码天然适合跨职能协作的团队。加上 JMeter 5 以后的接口测试能力越来越完善很多团队直接用 JMeter 同时承担了自动化接口测试和性能压测两条线的任务这一整合优势比很多工具都明显。2. 环境准备与 JMeter 安装2.1 JDK 8 的必要性与安装配置JMeter 是一个 Java 应用它的运行是离不开 JDK 的。请注意这里明确的是 JDK 而不是 JRE因为 JMeter 的某些功能模块比如使用 Java Request 编写测试逻辑时需要用到 javac 来编译源码。如果你是去官网下载源码包而不是二进制发行包编译过程也需要完整的 JDK。JMeter 5.x 系列官方要求的 JDK 版本是 Java 8我个人的建议是直接用 JDK 8 作为稳定长期支持的基线。虽然新出的 JMeter 5.6 可以跑在更高版本的 JDK 上但很多企业内部的依赖环境比如 Agent 节点、其他测试框架还停留在 Java 8为了避免兼容性问题还是稳妥一点更明智。在 Windows 上安装 JDK 时需要注意一个小细节就是不要装到带空格的路径下比如C:\Program Files\Java\jdk1.8.0_202这种路径在某些工具解析时会出诡异的问题。比较建议的安装路径是D:\Java\jdk1.8.0_202这样的纯英文路径。环境变量的配置是新手经常卡壳的地方。主要有三个维度JAVA_HOME D:\Java\jdk1.8.0_202这是供 JMeter 和 Tomcat 等工具找到 JDK 位置的关键变量。在Path变量中追加%JAVA_HOME%\bin这样可以在命令行里直接使用java -version和javac -version。可选设置CLASSPATH .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar虽然 JMeter 不一定需要但后续开发调试用得上。配置完成后打开命令行输入java -version如果能正常输出版本号说明环境就绪了。常听到有人问为什么配了环境变量却触发不了多半是配置后没有重新打开命令行窗口或者 Path 里路径带了多余的空格所致。2.2 JMeter 5.x 下载与目录结构解读下载 JMeter 时一定要去 Apache 官方项目页面而不是随便在搜索引擎上点一个下载链接。第三方站点经常捆绑一些乱七八糟的插件甚至可能内置恶意脚本。推荐路径是访问 Apache JMeter 官方网站进去之后选 “Download Releases”找到对应的二进制压缩包。Windows 系统选 zip 格式Linux 和 macOS 选 tgz 格式。下载完成后解压你会看到 JMeter 的目录结构其实很清晰。我列一下几个需要特别注意的目录bin里面存放了可执行脚本和配置文件jmeter.batWindows/jmeter.shLinux就是启动入口。lib是 JMeter 依赖 jar 包的核心区域。后续如果要引入第三方插件比如 PerfMon 性能监控、Custom Thread Groups或 MySQL 驱动都要把对应 jar 包放到lib/ext子目录下。extras里有一些辅助脚本比如 Ant 构建使用到的脚本一般用不到。logs目录用于存放运行日志压测过程中如果发现问题先看这里的jmeter.log能节省大量排查时间。强烈建议脚本都单独建一个目录存放不要和 JMeter 安装目录混在一起。JMeter 压缩包解压后到处都是 jar 和配置文件如果你把项目脚本混在里面时间一长会非常混乱而且升级 JMeter 版本时还容易误伤。启动 GUI 界面的方式是双击bin目录下的jmeter.batWindows或者执行./jmeter.shLinux / macOS。如果你发现启动后界面出现布局错乱、窗口控件重叠/撕裂的情况多半是 JDK 版本太新的渲染问题或 HiDPI 缩放异常。这种情况下可以在启动文件jmeter.bat中找到运行参数加上一句set JMETER_OPTS-Dsun.java2d.dpiawarefalse -Dswing.defaultlafcom.sun.java.swing.plaf.windows.WindowsLookAndFeel或者直接使用bin下的jmeter.bat右键管理员运行往往能解决问题。3. 压测脚本设计的核心细节3.1 线程组参数详解与场景建模JMeter 的脚本中测试计划是根节点线程组则是真正定义并发策略的地方。很多人对线程组的参数理解不够深入导致压测结果失真。我来拆解一下那几个关键参数Number of Threads (users)就是虚拟用户数也是决定并发量的基础。但请注意设置 1000 个用户并不等于服务器同一时刻接收了 1000 个并发必须结合Ramp-Up Period来看。Ramp-Up Period (seconds)表示在多少秒内启动这些线程。假设线程数是 100Ramp-Up 是 10那么节奏就是每秒启动 10 个线程发起请求。如果 Ramp-Up 设置过小线程会在瞬间全部涌出对服务器的冲击会很大也会掩盖真实的服务能力但设置过大又可能让测试结果过于平缓测不出上限。Loop Count循环次数。写infinite时适合需要持续压测的场景但要配合压测时长来控制总请求量。Same user on each iteration勾上后会模拟同一用户多次发请求适用于需要登录态的会话保持。我的习惯是第一次压测时先预估并发量确定线程数 50、100、200 这样的阶梯每次递增都观察 TPS 和响应时间的变化。Ramp-Up 设置成线程数的 1/10 或者 1/20 秒比较合理。比如 100 线程配 10 秒 Ramp-Up这样每秒新增 10 个用户不会造成瞬间流量毛刺。如果想要精确控制每次请求之间的时间间隔要在线程组里配置Constant Throughput Timer这个定时器它可以让 JMeter 按照你设定的目标吞吐量来发请求比粗暴地设置Think Time思考时间要准确得多。关于定时器的选择后续我在“控制器和定时器的最佳实践”一节再展开。3.2 HTTP 取样器配置与 HTTPS 证书处理HTTP Request 取样器是 JMeter 压测用的最多的请求组件。在填写请求参数时要避免一个常见误区只是将请求写成本地地址就直接开压这种压测结果没有价值。正确的做法是要把协议HTTP / HTTPS、服务器名称或 IP、端口、请求方法GET POST PUT DELETE、路径、请求体参数都真实体现出来。当被测服务是 HTTPS 时会牵涉到证书信任问题。JMeter 无法识别自签证书除非你通过 JMeter 自带的证书生成工具生成一个客户端证书导入或者在 JMeter 的system.properties文件里配置信任策略。最省心的一种方式是直接在 JMeter 的bin目录下执行installCert相关的功能也可以用浏览器手动把导出的.cer文件导入到 JDK 的cacerts证书库中。还有一种更简单的办法就是在 HTTP 取样器的高级设置里将Implementation选为HttpClient4然后在bin/jmeter.properties里找到server.rmi.ssl.disable相关的参数设置为true忽略证书校验。这个配置在开发环境或者内部压测时特别管用但注意别在生产环境乱用。另外还有一个容易被忽略的点Use KeepAlive选项。HTTP 协议是建立在 TCP 连接之上的如果压测脚本把 KeepAlive 关掉每次请求都要重新建连不仅压测数值会偏低还会给服务器增加无谓的连接开销。通常情况下把它勾上用来模拟浏览器实际行为除非你特意做的是长连接被关闭场景下的连接风暴测试。4. 断言与变量提取关键技术4.1 Beanshell 断言与断言失败处理压测脚本里只发请求不校验响应是毫无意义的。如果你的脚本跑出来所有请求都成功但服务器实际返回的是 500 错误页那么这个压测结果就是误导性的。所以断言是压测脚本里必不可少的一部分。JMeter 默认提供的断言组件有响应断言、JSON 断言、BeanShell 断言等。其中 BeanShell 断言是最灵活、也最考验基本功的一类。BeanShell 小脚本可以访问到 JMeter 内置变量比如prev表示上一次请求结果、ResponseCode、ResponseData、ResponseMessage等。举个实际例子假设接口返回的是 JSON 结构其中有一个code字段为 0 表示成功其它值都是失败。此时你在 BeanShell 断言中这样写import org.json.JSONObject; import org.json.JSONArray; String response prev.getResponseDataAsString(); try { JSONObject json new JSONObject(response); String code json.optString(code); if (!0.equals(code)) { Failure true; FailureMessage 业务失败, code code , msg json.optString(msg); } } catch (Exception e) { Failure true; FailureMessage 解析响应异常: e.getMessage(); }这个断言的写法我在项目中用得非常顺手因为系统接口的业务状态码并不总是 HTTP 状态码。如果你只在响应断言里判断文本 “success” 之类的关键词很容易误判。比如有些接口返回 HTTP 200但业务上却因为参数校验失败返回了业务错误码这个时候只有业务级断言才能保证压测请求真正“成功”。值得注意的是BeanShell 断言的性能相对偏低。如果单次压测的 QPS 非常高又大量使用了 BeanShell它可能成为 JMeter 客户端自身的瓶颈。这一点在小并发场景下没有感觉一旦到数千并发就会明显。所以能使用 JSON 断言就优先用 JSON 断言只有在复杂逻辑情况下才引入 BeanShell 脚本。4.2 JSON 提取器从响应中抽取动态参数接口压测时经常要串联场景先登录拿 token再带 token 查列表再根据列表里的 id 去下订单。这时候就需要从上一个请求的响应数据里提取参数传递给下一个请求。JSON Extractor是 JMeter 提供的最强参数提取组件之一。它的配置有四要素Name of created variables自定义变量名比如tokenJSON Path expressions比如$.data.tokenMatch Numbers0 表示随机匹配1 表示第一个匹配结果-1 表示提取全部Default Values提取失败时的默认值防止后续请求直接报错举个例子登录接口返回的数据长这样{ code: 0, data: { token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9, userInfo: { userId: 10086 } } }JSON 提取器里配置$.data.token为变量authToken然后在后续请求的 HTTP Header Manager 里添加Authorization: Bearer ${authToken}这样就完成了场景串联。提取动态id时常常会遇到响应是一个 JSON 数组的场景。比如列表接口返回{ data: [ {id: 101, name: 测试商品A}, {id: 102, name: 测试商品B}, {id: 103, name: 测试商品C} ] }如果你要循环下单需要提取全部 id 并依次使用这个时候 JSON 提取器设置Match Numbers为-1变量名写itemIdsJMeter 会自动生成itemIds_1、itemIds_2、itemIds_3。在循环控制器中通过vars.get(itemIds_ i)的方式一一取出或者用__V(itemIds_${__counter(,)})拼接。还有一种实现方式是使用 Groovy 配合JsonSlurper在 JSR223 后置处理器里做更灵活的提取这一点比 BeanShell 性能更好。其实我更推荐 JSR223 Groovy它有更好的缓存优化能力而且语法友好可读性强。但如果你完全不懂编程JSON 提取器的鼠标操作方式也足够了。4.3 正则表达式提取器的兜底方案虽然 JSON 提取器处理 JSON 格式很优雅但有时候接口返回的是 HTML 页面或者其他格式这时候正则表达式提取器就是更通用的选择。正则提取器以()?模式来查找匹配内容最典型的用法是引用名称写csrfToken正则表达式写namecsrf value([^]*)模板写$1$匹配数字填 1模板里的$1$表示取第一个捕获组。如果匹配到多个值可以通过设置匹配数字来控制取第几个。这个方案在录制 Web 脚本时的 CSRF Token 提取非常管用。用正则提取有个小教训千万不要把正则写得太宽泛或太严格。太宽泛会把不相关的内容也提取进来太严格则会在响应结构稍微变化时提取失败。建议先在响应数据的查看结果树中用RegExp Tester模式试验匹配结果确认 OK 后再保存配置。5. 录制 HTTPS 脚本与常见环境问题5.1 JMeter 代理录制 HTTPS 脚本的原理虽然手写脚本在大多数场景下很高效但遇到一个流程特别长、包含大量异步请求的页面操作时手写反而容易遗漏请求。这时候用 JMeter 自带的 HTTP(S) Test Script Recorder 录制会方便很多。录制原理是JMeter 在本地启动一个代理服务器默认端口 8080你让浏览器或手机通过这个代理访问被测系统浏览器发出的每一个请求都会被 JMeter 捕获并生成对应的取样器。录制 HTTPS 脚本时会遇到 SSL 证书信任的问题。因为 JMeter 代理服务器需要扮演“中间人”角色来解密 HTTPS 流量它必须给浏览器提供一个根证书。操作步骤如下在 JMeter 的bin目录下找到ApacheJMeterTemporaryRootCA.crt文件。将该证书安装到浏览器的“受信任的根证书颁发机构”中。工具栏中启动 “HTTP(S) Test Script Recorder”设置录制目标使用本机端口 8080。浏览器或客户端代理指向本机 8080访问被测系统即可录制。在 Windows 上导入证书时要注意如果只是导入到“个人”证书存储区Chrome 可能仍会提示不安全需要导入到“受信任的根证书颁发机构”这一层。另外录制的 HTTPS 脚本经常会出现证书域名不匹配的问题如果被测系统用的是 IP 地址访问推荐在 HTTP Request 取样器的高级里勾选Use KeepAlive旁边对应的那个 “源地址” 设置或者干脆忽略证书校验把bin/jmeter.properties中server.rmi.ssl.disabletrue。录制的脚本通常很“脏”比如会包含大量静态资源图片、CSS、JS。这些请求对业务压测没有价值还会拖低压测机的性能。录制完成后建议用正则和筛选器把不需要的请求剔除掉只保留核心业务接口再按实际流程调整逻辑顺序。5.2 上传文件与中文文件名乱码压测上传文件接口时会遇到两类麻烦文件路径怎么配以及中文文件名乱码。上传文件的操作不复杂HTTP Request 取样器的Files Upload选项卡中配置三个参数文件名称本地路径、参数名称、MIME 类型。JMeter 会自动读取文件内容作为请求体的一部分。但这里有个容易踩坑的点文件路径如果是相对路径那么它相对的是当前工作目录而不是 JMeter 脚本所在目录。为了避免不同机器执行时文件找不到强烈建议用绝对路径或者在测试计划中自定义一个属性来动态拼接路径。至于中文文件名乱码这是 JMeter 老用户都知道的坑。HTTP 请求本身用的编码取决于Content-Type里是否带charsetUTF-8。如果你没有显式方式提交文件名默认按 ISO-8859-1 处理中文自然乱码。解决办法有两个方向在取样器的“内容编码”字段里统一配置utf-8这样请求体会按 UTF-8 编码。在/bin/jmeter.properties中统一修改sampleresult.default.encodingUTF-8。但要小心设置编码后需要同时检查前置接口返回的数据是否也按 UTF-8 解析否则前一个接口提取的参数在后续请求中也会出现乱码传导。另一个更隐蔽的问题是 HTTP 请求头里的Content-Disposition中的 filename 是 RFC 5987 的编码方式。有些服务器要求 filename* 才有 UTF-8 编码而 JMeter 在构造 multipart 请求时不会自动做标准编码。如果你遇到后端一直收不到中文文件名需要手动在后置脚本中重写filename比如使用 Groovy 处理URLEncoder.encode(fileName, UTF-8)再替换到请求参数中。5.3 环境变量与界面显示异常Win7 上配置 JMeter 环境变量经常碰到的怪问题就是启动时报java.net.SocketException: Permission denied: connect。这种一般不是 JMeter 本身的问题而是 JDK 版本和防火墙策略的纠缠。此外Win7 上如果 JMeter 版本太新比如 5.6可能会因为 API 兼容性问题无法启动建议安装较新的 Windows 补丁或直接切换到 JDK 8 的最新小版本。界面布局错乱的问题除了前面提到的 HiDPI 参数还可能源于 JMeter 引用了过多插件之后出现的 jar 包冲突。建议把lib/ext下面不需要的插件清理掉保持最小化依赖。另外在使用命令jmeter.sh -n -t xxx.jmx跑压测时如果你是在 SSH 里开的窗口大小没必要设置很宽因为它根本不走 GUI。界面布局错乱不影响命令行压测的结果不用太焦虑。6. JMeter 压测的完整实操流程6.1 使用非 GUI 模式进行压测生产环境压测时千万不要开 GUI 界面执行测试。GUI 模式会消耗大量客户端资源画图渲染和采样数据存储会拖慢压测引擎导致结果失真。我接待过不少客户他们压测 2000 并发的时候发现 JMeter 自己先崩了一看都是开了图形界面跑的。正确的压测姿势是使用命令行模式jmeter -n -t /path/to/test.jmx -l /path/to/results.jtl -j /path/to/jmeter.log -e -o /path/to/HTML_Report我来解释一下这些参数的意义-n表示非 GUI 模式运行-t指定要执行的 JMX 脚本-l指定结果文件的保存路径后缀一般为 .jtl-j指定日志文件路径排错时用-e -o表示生成 HTML 格式的报告-o后接输出目录压测执行中要实时查看状态可以用-r参数和远程负载机配合使用。如果只是单机执行则是-n -t足够。有一个关键细节结果文件格式jtl中默认保存的是采样数据可能非常大。如果你只是想要吞吐量和响应时间的聚合数据可以在jmeter.properties中配置只保存必要字段比如jmeter.save.saveservice.output_formatcsv jmeter.save.saveservice.thread_countstrue jmeter.save.saveservice.bytestrue jmeter.save.saveservice.timestamptrue jmeter.save.saveservice.successfultrue千万注意修改 properties 配置后要重启 JMeter 或重新启动命令行进程才会生效。压测完成后-e -o生成 HTML 报告非常直观里面有 APDEX 指标、请求统计表、耗时分布图等。这个报告可以直接整合到 CI 流程作为性能测试输出物我们团队就用过 Jenkins 里调用 JMeter 命令后发布 HTML 报告一整套流程跑得非常顺。6.2 分布式压测主从模式的落地经验当你单台压测机性能不足以支撑高并发时比如跑线程数到 3000 时JMeter 客户端 CPU 已经 100%就必须考虑分布式压测。JMeter 的分布式压测模型是简单的主从结构Master 节点负责发起任务和汇总结果它不直接跑脚本。Slave 节点也叫 Agent负责实际压力产生在各自的机器上运行 JMeter Server。配置步骤在所有 slave 机器的jmeter.properties中设置server.rmi.port1099。在 master 机器的jmeter.properties中把remote_hosts设为 slave 的 IP 和端口以逗号分隔。启动 slave 机器上的jmeter-server或jmeter-server.bat。在 master 上用-r选项远程启动所有代理。分布式压测最常遇到的问题是 RMI 通信失败尤其是跨防火墙环境。解决方案是在jmeter.properties里设置server.rmi.localportxxxx固定本地监听端口并确保防火墙放行 1099 和自定义监听端口。如果网络实在受限还有一个“土办法”直接把 JMX 脚本考到每台 slave 上用命令行同步启动再定时汇总结果。不过这样做不优雅结果对齐也麻烦优先级不高。分布式压测中最为关键的调优点是避免 Master 成为瓶颈。即便压力由 slave 产生所有的采样结果仍旧会通过网络回传到 Master 并异步落盘。如果采样量过大Master 进程容易假死。建议把结果文件保存为 CSV并只保存必要的字段最大程度减小回传数据包体积。必要时可以在每台 slave 上分别生成结果文件压测结束后再手工合并但要注意时间戳对齐否则统计会不准确。6.3 压测结果分析与性能瓶颈定位压测结束拿到一堆数字以后最关键的是判断系统到底行不行。先把响应时间和 TPS每秒事务数两个指标放一起看如果 TPS 高、响应时间平稳系统健康。如果 TPS 很低、响应时间急剧拉长大概率是服务端处理能力受限常见瓶颈在数据库连接池、线程池满、GC 频繁、网络带宽堵住。如果 TPS 波动剧烈说明服务端不够稳定多半是缓存失效、定时任务抖动或慢 SQL。分析阶段不要只停留在 JMeter 自带的聚合报告上更好的做法是配合服务端的性能监控数据。JMeter 的 PerfMon Metrics Collector 插件可以通过配置jpgc - PerfMon Metrics Collector监听 Agent 来实时采集被压机器的 CPU、内存、磁盘 IO 等数据。当然生产环境不能轻易装监控 Agent那你就需要通过系统自带工具或者 Prometheus 那套体系来观察。下面是我在实际压测中反复见到的一些套路性瓶颈和对应的排查方向做成速查表方便你参考现象可能瓶颈点排查方向TPS 上升缓慢响应时间呈指数级增长应用线程池大小不足查看 Tomcat / Spring 线程池配置以及线程被阻塞的堆栈请求全部超时服务端 CPU 不高数据库连接池被占满通过SHOW PROCESSLIST或 DBA 视图确认等待连接的数量内存缓慢上涨Full GC 频繁JVM 堆参数不合理分析 GC 日志观察堆使用情况是否成为锯齿形大量 Connection reset / Broken pipe网关超时断开检查 nginx / SLB 超时配置和长连接 keep-alive 值响应时间均值正常但 P99 很高存在少量慢请求线程阻塞在某个共享资源压测时启用 APM 追踪看慢调用链这里特别提醒一点不要一上来就优化代码。先要通过分层排除法分清瓶颈在网络层、数据层还是应用层。我们团队有个习惯做法先用低并发压 5 分钟看基线然后翻倍并发继续压同时监控服务端指标逐渐逼近拐点。这个过程能很快把瓶颈定位到具体某一层。7. JMeter 进阶技巧与实用插件7.1 关键控制器与定时器的正确姿势压测场景里经常需要模拟多个分支逻辑比如一定比例的用户访问首页另一部分用户直接访问商品详情页。这时候Switch Controller或Weighted Switch Controller就派上用场了。JMeter 原生提供的Throughput Controller可以按百分比控制请求占比比如设置Percent Executions为 30则有 30% 的吞吐量进入该分支。不过如果你的场景里有多个事务更优雅的是用jpgc - Ultimate Thread Group插件来实现阶梯加压、持续时间和暂停时间它能绘制非常直观的场景波形图。定时器的使用也要讲究时机。Constant Throughput Timer放在一个事务控制器下时可以有效控制吞吐量上限防止 JMeter 以无限制的最大速率打爆服务端。但它并不能精确控制每个线程的请求间隔因为它本质上是基于全局时间窗做调度。要模拟用户思考时间还是得靠Gaussian Random Timer避免所有线程同时点击形成瞬时尖峰。还有一个常见的低级错误把定时器放到线程组下导致每个取样器之间都会强制执行等待时间压测机发请求的节奏被严重拖慢。正确做法是把定时器放在需要间隔的取样器子级或者放到逻辑控制器内部让它只作用于特定环节。7.2 用 Groovy JSR223 替代 Beanshell贝利用 Beanshell 写断言和逻辑时如果遇到性能瓶颈解决办法是用 JSR223 取样器或 JSR223 断言并选择 Groovy 语言。Groovy 的优势体现在两点JMeter 5.x 内置了 Groovy 引擎不需要额外安装插件。Groovy 脚本在 JMeter 中会被缓存为ScriptEngine实例而 BeanShell 则每次动态解释执行效率差距巨大。以 CSV 参数化读取为例用 Groovy 在 JSR223 预处理中读取一行数据并设置变量写法如下import org.apache.commons.csv.* File file new File(/data/users.csv) def rows new CSVParser(file.getText(UTF-8), CSVFormat.DEFAULT.withHeader()).iterator() int idx vars.get(__threadNum).toInteger() % 1000 // 省略具体循环取值细节实际应用时需要维护当前指针当然日常参数化用得最多的还是CSV Data Set Config它已经相当成熟。如果你的参数文件中包含几十万行账号每次请求读取下一行需要开启Sharing Mode为All threads保证线程组所有线程共享同一个文件指针防止数据重复分配。7.3 常见插件的安装与最佳实践插件在 JMeter 生态中扮演了非常重要的角色。最常用的插件包是jmeter-plugins-manager安装之后可以通过 GUI 界面一键安装和升级插件省去手工下载 jar 的麻烦。我平时常用的插件清单Ultimate Thread Group支持精细的加压、持续、释放配置。PerfMon Metrics Collector压测时监控服务器的 CPU、内存、网络、磁盘。JSON Path Extractor其实已经集成在核心中了不需要费力安装。Custom JMeter Functions提供一些额外的内置函数比如计算 MD5、时间戳等。Random CSV Data Set支持随机读取 CSV 行来模拟随机用户数据。安装插件后特别注意版本冲突。有些插件是面向 JMeter 4.x 开发的在 JMeter 5.x 上虽然能运行但可能会引入不必要的告警日志甚至导致 GUI 崩溃。遇到这种情况直接把lib/ext下对应的 jar 删掉重启即可。8. 压测执行前的必备检查列表每次在正式压测前我都会强制自己过一遍下面的检查清单不要嫌麻烦避免压完数据出来了却发现条件不对最重要的是会导致信任崩塌。压测脚本中的断言是否正确覆盖了业务成功状态而不是只关心 HTTP 200。请求协议、端口、路径是否于生产或预发环境一致。压测机的网络带宽是否充足会不会出现网卡打满导致结果失真。服务器端的防火墙、安全组是否会掐断 JMeter 的请求连接。是否开启了 KeepAlive是否设置了合理的超时时间。参数化数据量是否足够会不会把同一个数据循环使用导致冲突。压测机的系统时间是否和服务器保持一致否则看监控曲线对不齐。是否已经关闭不必要的浏览器页面、系统更新、杀毒软件等占用资源的进程。上面每一条背后都有血泪教训。举一个最典型的例子曾经有一次我们压测一个抢购接口压测脚本里把用户 ID 写死成同一个结果数据库唯一索引直接冲突测试一跑就被大量 500 报错淹没。直到排查数据库慢日志时才发现这个问题——不是服务器撑不住而是压测数据太假。后来我们改成从百万级用户表中随机抽取 ID 做参数化测试结果才真正反映了系统能力。所以压测本质上是在模拟真实流量越贴近实际越好。参数化、CSV 随机读取、Think Time 的设置都是为了让压力模型更真实。9. 常见问题与排查技巧实录9.1 聚合报告中的响应时间为什么偏高有很多人问过我为什么我在 GUI 里跑的时候响应时间均值和命令行压测的结果差那么多原因在于聚合报告统计时JMeter 对网络连接时间、DNS 解析时间、请求发送耗时等都算进响应时间里而且 GUI 本身会引入大量的渲染开销。更准确的分析应该用Transaction Controller包裹业务请求单独定义事务时间并在-e -o生成的 HTML 报告里查看事务响应时间。还有另一个影响因素就是监听器本身也会消耗资源。如果你在 UI 里同时挂载了View Results Tree和Aggregate Graph每个采样点都要保存响应数据做显示高频压测时这会显著拖慢请求发送速率。所以正式压测时一定要去掉这类监听器或者将jmeter.properties中的modeStrippedBatch改为Batch模式。9.2 JTL 文件过大与资源占用过高压测时间很长时JTL 文件动辄好几个 GB落盘和回传都会成为性能瓶颈。解决办法之一是采用样本过滤属性将jmeter.save.saveservice配置为仅保存必要内容不要整个 response 都写进文件。如果压力特别大可以设置jmeter.save.saveservice.print_field_namestrue和jmeter.save.saveservice.output_formatcsv并让每个线程组配置不同的文件名后续用脚本按列合并。JMeter 自身的堆内存也需要调整。大压测任务默认的堆内存只有 512M 或 1G远远不够。修改bin下jmeter.bat/jmeter.sh中的HEAP-Xms1g -Xmx2g -XX:MaxMetaspaceSize512m。如果你跑分布式Master 节点主要做结果聚合对内存要求高slave 节点主要跑线程并发量本身大同样需要调大内存否则线程还没完全启动压测机先 OOM 了这种事我遇到不止一次。9.3 SSL 证书相关异常排查HTTPS 压测时经常碰到报错SSLHandshakeException: PKIX path building failed。遇到这类问题最直接的解释是 JMeter 的 JVM 不信任服务器证书。解决方案前面提到过要么把服务端证书导入 JDK 的cacerts证书库要么直接忽略校验。导入命令大致如下keytool -import -alias myserver -keystore $JAVA_HOME/jre/lib/security/cacerts -file /path/to/server.cer -storepass changeit这个命令会把服务器证书添加到 JVM 的全局信任库中。记住-storepass默认是changeit如果你之前改过得用实际密码。还有一种情况是证书链不完整服务器端只返回到根证书、缺少中间证书JVM 无法完成链校验。这需要在服务端修复证书链配置。压测端可以做的是在 JVM 参数中加入-Dcom.sun.net.ssl.checkRevocationfalse来跳过撤销检查但这仅仅是绕过了部分问题证书链缺失本身代表服务器配置有问题压测即使能跑通录制的 HTTPS 脚本也会在后续请求中继续出现异常。9.4 上传文件时中文文件名乱码案例复盘我曾帮一个项目排查过前端上传头像接口的压测问题所有上传请求都成功但服务端保存的文件名全部变成了类似?????.jpg的乱码。排查过程是这样的我先在 JMeter 里直接用Files Upload试了几个文件名发现是乱码于是断定不是业务代码问题而是 JMeter 构造 multipart 请求时没有正确编码文件名。我抓包看到filename测试头像.jpg在 HTTP 请求体里是 ISO-8859-1 编码不是 UTF-8所以服务端在解析 multipart 时按照错误的解码方式解析就变成了问号。最后解决方式是在 JSR223 预处理脚本里构造整个 multipart 请求体或者修改 JMeter 源码级别的 HTTP 请求参数编码方式。更省心的办法是升级到 JMeter 5.4 以后的版本它的 multipart 编码逻辑有所改善。另外一个折中方案是后端临时改一下接口逻辑增加一个Content-Disposition的filename*解析分支但这就变成业务方去适配压测工具了不太推荐。10. 从压测数据到系统调优的闭环压测的最终目的不是找出问题就完事而是推动系统性能优化。当你在 JMeter 报告中发现响应时间超标时要立刻和开发协作把问题定位到具体接口和代码层面。这一步通常需要结合服务端日志和链路追踪工具。举一个我之前服务过的电商项目案例我们压测一个库存扣减接口100 并发时接口响应时间平均是 35ms但当并发升到 500 时响应时间突然飙到 1.2 秒TPS 却在原地踏步。第一反应是数据库竞争问题但看 CPU 和数据库连接都没有异常。后来通过链路追踪发现瓶颈在一个叫做receiveNotif的 MQ 消费逻辑上如果消费端处理不过来消息堆积导致线程阻塞那接口就慢。优化的方向是增加消息消费并发数并调整消费批次大小。调优后再压测500 并发下响应时间回到了 80ms 左右TPS 也提升了一倍。这个案例说明了JMeter 给了你一个入口但你能不能通过它让系统变快取决于你是否能把压力结果和服务端内部运行状态关联起来。重新说一下 APM 工具的重要性像 SkyWalking、CAT、Pinpoint 这类开源的链路追踪工具配合 JMeter 做压测能够在压力上升时自动生成调用链指出慢在哪一步、哪个基础组件调用耗时最长。这是整个性能工程闭环里非常重要的一环。持续做性能基线回归也能靠 JMeter 完成。团队可以约定每周跑一次固定的压测脚本生成报告后和上周对比一旦响应时间偏离超过 10%就触发告警和人工介入。这样能在上线前就捕捉到底层代码的劣化趋势而不是等到线上用户大量投诉。最后说一点实践经验压测工作最怕的是数据不真实、过程不可复现。所以我强烈建议每个压测项目都留下完整的记录包括测试脚本版本、并发模型、线程数、Ramp-Up、结果摘要、服务端配置变更不仅在团队内部有据可查遇到线上问题回溯时也能快速定位是配置调整导致还是流量波峰引起。JMeter 脚本虽然是明文 XML但建议纳入版本控制它和代码一样需要 review 和管理。从环境搭建到脚本设计再到分布式执行和结果分析JMeter 的性能压测能力是足够深厚的。你不需要把它每个功能都吃透才敢用掌握核心的线程组、HTTP 取样器、断言和参数化再配合我上面提到的检查列表和排查思路就能解决日常绝大多数压测需求。后续随着你对 Groovy 脚本、自定义插件和分布式压测逐渐熟练还可以在自己团队内建立一套可持续复用的性能测试基座让压测从一次性活动变成日常研发流程的一部分。
返回列表