Jmeter 录制压测脚本这件事,表面看只是"点几下、把业务流程走一遍",真上手就知道四种常见路子各有各的脾气:有的两分钟就能出脚本,五分钟就报证书错误;有的稳得像块石头,但光配证书就能劝退一半人。我从早期内网的老 B/S 系统一路做到云上微服务集群的并发验证,前前后后换过至少四种录制方式,踩过的坑攒了满满两页笔记,这篇就把这四种方式的原理、边界、完整操作步骤和排查经验一次交代清楚。
先把概念划清楚。性能测试里说的"录制",指把浏览器或客户端真实发出的 HTTP/HTTPS 请求流量捕获下来,转成 Jmeter 可以直接执行的 .jmx 脚本。这跟直播圈里说的"录制"完全不是一回事,后者是把推流画面存成文件,跟压测八竿子打不着,所以标题里只写"录制"两个字,经常有朋友点进来才发现走错门,这里先说明白。
读完之后你能拿到什么:四种录制方式各自的完整操作流程、一张选型对比表、录制完成后的脚本改造思路(参数化、关联、断言、定时器),外加一份录制与加压阶段的高频问题速查表。刚接触 Jmeter 的同学可以照着步骤一步步走,已经在做性能测试的同行可以直接跳到选型和排查那两节抄作业。
1. 录制方式怎么选:先看四种路子的底层机制
1.1 手工拼接口和录制脚本,各管哪一段
很多人一上来就问"到底该录还是该手写",这个问题其实问偏了。真实项目里,这两件事从来不是二选一,而是分工。录制负责把业务流程的骨架搭出来,手写负责把骨架上的关节补全。
我自己的判断标准很朴素:一个业务场景如果涉及超过八个接口串联,或者链路里带着前端框架异步刷出来的动态参数,那纯手写的成本会高得离谱。一个下单流程,从登录、选商品、加购物车、算价、下单、支付回调到查订单,中间还夹着一堆埋点和轮询接口,手工一个个拼,光抄参数就要大半天,还容易漏掉某个中间请求。这种情况下录制就是省时间的正路,二十分钟录完,再花两三个小时清理和关联,整体比手写快得多。
反过来说,如果只是一个单接口的压测,参数结构清楚、没什么依赖,那直接手写反而更干净。录制出来的脚本会带一堆冗余的 HTTP 头、无关的采样器和没用的断言,清起来也麻烦。所以判断逻辑是:链路长、依赖多、需要复现真实用户行为,用录制;链路短、参数固定、需要精确控制并发模型,用手写。
还有一个容易被忽略的点:录制出来的脚本从来不等于能用的压测脚本。它只是一个"请求序列快照",里面所有动态生成的东西都是死的。回放的时候服务端一校验,要么报参数无效,要么直接返回登录失效。所以真正的功夫在录制之后,这一点后面第 7 节会展开讲。
1.2 四种录制方式的横向对比表与选型口诀
市面上流传的"四种方式"版本不少,但真正在项目里能落地的就这么四类。我把它们的核心机制、代价和适用边界整理成一张表,选型的时候直接对着看。
| 录制方式 | 核心原理 | HTTPS 支持 | 移动端/App | 脚本干净度 | 典型适用场景 |
|---|---|---|---|---|---|
| Badboy 录制 | 内置浏览器内核直接录制,导出 JMX | 依赖内置内核,老站点可用 | 不支持 | 中等,冗余较多 | 老式表单型 B/S 系统 |
| Jmeter 自带代理录制 | 本地起 HTTP 代理,拦截浏览器流量 | 需要装根证书,支持完整 | 手机设代理后支持 | 高,可控过滤 | 主流 Web 项目,链路复杂 |
| 抓包工具转 JMX/HAR | 抓包后导出会话再转换 | 抓包工具侧处理证书 | 支持,App 场景主力 | 中等,需要二次清洗 | 移动端、只录特定接口 |
| 浏览器扩展录制 | 扩展监听浏览器网络栈 | 天然支持,无需装证书 | 不支持 | 一般,可能带插件依赖 | 快速出草稿、纯浏览器流程 |
选型口诀我总结成三句。第一句是"Web 项目优先用自带代理",因为过滤规则、目标控制器、分组策略都在 Jmeter 内部,录完就是干净的原生元件,不会引入第三方插件依赖。第二句是"App 和只录局部用抓包",因为手机端流量绕不开抓包工具,而浏览器的 DevTools 可以直接导出 HAR,零依赖零安装。第三句是"要快就上浏览器扩展",代价是导出的脚本可能带着别人家的元件类,打开报错还得补插件。
Badboy 我把它放在最后一档。不是说它不能用,而是它的内置浏览器内核实在太老,今天大部分前端工程在它里面根本渲染不出来,录出来的脚本常常只有一条主文档请求。留着了解可以,新项目别指望它。
提示:无论用哪种方式录,录之前先把浏览器缓存清掉、无关标签页关掉、代理只开一个。多开一个代理插件,流量走岔了,录出来的东西会让你怀疑人生。
2. 动手之前:Jmeter 环境搭建与几个必改的默认配置
2.1 JDK、安装包与目录选择的三个硬约束
Jmeter 本身是个纯 Java 应用,所以第一件事是把 JDK 装好。新版本的 Jmeter 对 JDK 版本有要求,稳妥的做法是装 JDK 8 或者 JDK 17 这两个长期维护版本,别用太新的实验版本,否则某些插件会加载失败。装完之后命令行敲java -version,能正常输出版本号才算过关。这里有个新手常犯的错误:只装了 JRE 没装 JDK,运行 Jmeter 的时候报找不到编译器,其实是你少了开发包。
第二个约束是解压目录。Jmeter 是绿色包,官网下载压缩包解压即用,不需要安装程序。但解压路径绝对不能带中文、空格和特殊符号。这不是洁癖,是实打实的坑:Jmeter 在录制 HTTPS 的时候会在 bin 目录下生成临时根证书文件,路径里有中文或者空格,证书生成这一步直接失败,界面上还只给你一句含糊的错误提示,查半天查不出来。所以老老实实解压到D:\tools\apache-jmeter-5.6.3这种纯英文短路径下。
第三个约束是环境变量。把 bin 目录加到 PATH 里,同时配一个 JMETER_HOME 指向解压根目录。这样在任何目录下都能直接敲 jmeter 命令,脚本和结果文件可以按项目分目录管理,而不是全堆在 bin 里。配完环境变量记得重开一个终端窗口,旧窗口读不到新变量,这个低级问题我自己也犯过两次。
# 进入解压目录的 bin 下启动图形界面(仅用于录制和调试) cd /d D:\tools\apache-jmeter-5.6.3\bin jmeter.bat # Linux / macOS 下非 GUI 模式压测,输出 jtl 结果并生成 HTML 报告 jmeter -n -t order.jmx -l order.jtl -e -o ./report2.2 语言、字体、内存与插件的初始化配置
第一次打开 Jmeter,界面是英文的,菜单里 Options 下面有一项 Choose Language,切成中文之后重启就生效。但更稳的做法是直接改配置文件,因为 GUI 里切的语言有时候重启会丢。打开 bin 目录下的 jmeter.properties,把语言和编码这两行配上。
language=zh_CN sampleresult.default.encoding=UTF-8 jmeter.save.saveservice.output_format=csv jmeter.save.saveservice.response_data=false jmeter.save.saveservice.samplerData=false后面两行是压测阶段的重点。结果文件默认会把每个请求的响应体和请求体都存下来,压几百个并发的时候,这个文件能涨到几个 G,磁盘直接写满,压测机先趴下。所以正式压测前一定要把这两项关掉,只在调试期临时开。
字体小这个问题也很烦人,尤其是脚本里有中文的时候看着费劲。Jmeter 的界面字体受外观主题影响,可以在 Options 里换 Look and Feel,或者改 jmeter.properties 里的界面字体相关参数。另外脚本编辑区的字体是通过jsyntaxtextarea.font.size这类参数控制的,改成 16 或者 18 舒服很多,长时间盯着脚本眼睛能少受点罪。
内存参数是压测机的命门。Windows 下编辑 jmeter.bat,Linux 下编辑 jmeter 脚本,找到设置堆大小的那一行,把默认值调大。默认往往只有 1G,做几百并发的压测,几个来回就 OutOfMemoryError。我一般按压测机物理内存的一半来配,比如 16G 内存的机器给到 8G,同时把新生代比例适当调一下。改完记得用非 GUI 模式跑,图形界面本身就吃内存,正式压测时开界面等于白白浪费一半资源。
插件管理器的安装也不能漏。把 jmeter-plugins-manager 的 jar 包丢进 lib/ext 目录,重启 Jmeter,Options 下面会多出一个 Plugins Manager。后面要用的 JSON 相关元件、性能指标图表、以及某些录制扩展导出脚本后需要的依赖,都靠它装。装插件的时候有个小技巧:先看插件列表里的更新时间,选最近还在维护的版本,老插件在新版 Jmeter 上经常加载失败。
3. 方式一:Badboy 录制法,以及它为什么慢慢退场了
3.1 从打开页面到导出 JMX 的完整流程
Badboy 的定位很明确,它是一个自带浏览器的录制工具,免费、体积小、不需要额外配置代理和证书。操作流程简单到几乎没有学习成本。
打开 Badboy 之后,界面左边是请求树,中间是页面预览,顶部是地址栏和录制控制条。在地址栏输入被测系统的入口地址,敲回车,页面加载完成后点顶部那个红色的圆形录制按钮,然后就像正常用户一样在页面里操作:登录、点菜单、填表单、提交。所有操作都会被记录到左边的树形结构里。全部走完之后点停止按钮,录制就结束了。
接下来的关键一步是导出。在左侧请求树上右键,或者从 File 菜单里找到 Export to Jmeter 这一项,选择保存路径,生成一个 .jmx 文件。这个文件可以直接被 Jmeter 打开,打开后你会看到一串 HTTP Request 采样器,顺序和你在页面上的操作顺序一致。速度确实快,整套流程十分钟以内能搞定。
Badboy 早期的口碑很好,主要是因为它解决了"手写太累"的问题,而且不需要折腾代理和证书。在它还活跃的年代,很多性能测试教材里都把它作为首推的录制方案。它的请求树结构也让初学者很容易理解"一个页面操作对应若干个 HTTP 请求"这件事,学习价值不低。
3.2 内核老旧带来的三个典型失败场景
问题出在它的浏览器内核上。Badboy 内置的是很老的 IE 内核,而今天的前端工程大量使用现代框架,页面的渲染和数据加载都依赖新的浏览器能力。这就导致三类典型的失败。
第一类是页面白屏。访问一个用现代框架搭的后台系统,地址栏回车之后页面一片空白,什么都渲染不出来,录制树上只有一条主文档请求。原因很简单,内核太老,脚本报错,页面初始化直接挂掉,自然也不会有后续的接口请求被录下来。
第二类是异步请求丢失。有些页面看起来能显示,但列表数据、下拉选项全是空的。这些数据是通过异步请求拉回来的,老内核里这部分逻辑没跑通,请求根本没发出去,录出来的脚本自然缺了最关键的那几条。等你回放的时候会发现登录完之后就断了,因为后面的接口压根没录到。
第三类是 HTTPS 站点报错。目标系统用了 HTTPS,Badboy 在握手阶段就失败,页面加载不出来,或者弹出一堆证书警告,手点忽略之后页面还是有问题。
所以结论很清楚:Badboy 只适合那些还在用传统表单提交、没有复杂前端逻辑、并且是 HTTP 或者证书兼容性没问题的老系统。新项目我基本不用它,同样的思路用浏览器扩展来做,内核是现代的,成功率完全不是一个量级。
提示:如果你手上的系统确实是老式 B/S 架构,Badboy 录出来的脚本也别直接压。它会把每个请求的完整头信息抄下来,包括一些跟会话绑定的字段,回放时照样会失败,该做的关联一步都不能省。
4. 方式二:自带代理录制(HTTP(S) Test Script Recorder)全流程
4.1 代理录制的原理与录制器参数详解
这是 Jmeter 官方提供的正统方案,也是我在 Web 项目里用得最多的一种。原理说穿了不复杂:Jmeter 在自己机器上启动一个 HTTP 代理服务器,监听某个端口,然后让浏览器的所有流量都从这个代理走。每一个流经代理的请求,Jmeter 就把它转成一个 HTTP Request 采样器,按顺序塞进指定的位置。
配置入口在测试计划上右键,Add 下面找 Non-Test Elements,就能看到 HTTP(S) Test Script Recorder。加进来之后有几个参数必须逐项确认。
端口是第一个。默认给的 8888 一般不会冲突,但如果撞上了别的服务,就换一个比如 8899。端口被占用的报错很直接,启动的时候日志里会写地址已被使用,换一个就好。
目标控制器是第二个,决定了录下来的采样器往哪里放。默认会放到录制器自己下面,但我更习惯提前建好一个线程组,然后把目标控制器指向那个线程组。这样录完之后结构清晰,不用再手动搬一遍。
分组策略是第三个,也是最容易被忽略的一个。录制器会根据两个请求之间的间隔时间判断要不要新建一个分组,常见的选项包括不分组、每组加一个分隔符、每组生成一个事务控制器等等。选"每组生成事务控制器"最实用,因为它能按操作节奏把请求自动归成一个个事务块,后面做事务响应时间统计的时候省事。这个间隔时间由 jmeter.properties 里的参数控制,默认值偏短,业务操作慢的时候会切得太碎,可以适当调大一点,一般一千到五千毫秒之间按你的操作节奏试。
头部捕获和重定向选项是第四个。是否录制 HTTP 头信息、是否自动跟随重定向,这两项要按目标系统的行为来定。如果系统内部有跳转,建议让录制器跟随并把重定向后的请求也录下来,否则脚本里会缺链路。
4.2 HTTPS 根证书的生成、安装与信任链打通
HTTPS 是这条路线上最大的一道坎,也是百分之八十的人卡住的地方。原理其实很容易理解:Jmeter 作为代理,需要在中间解密流量才能看到请求内容,而解密的前提是浏览器信任 Jmeter 的证书。所以 Jmeter 会自己生成一张临时根证书,你需要把它装到浏览器或系统的信任列表里。
点击录制器上的 Start 按钮之后,Jmeter 的 bin 目录下会生成一个证书文件,名字类似 ApacheJMeterTemporaryRootCA.crt。弹出的小窗口会提示你证书的有效期,默认只有七天左右,所以这个操作不是一次性的,隔一段时间要重新生成和安装。
安装到 Windows 系统的流程是:双击这个 crt 文件,选择安装证书,存储位置选本地计算机,下一步选择"将所有的证书都放入下列存储",浏览找到"受信任的根证书颁发机构",确认导入。中间会弹一个安全警告,点确定就行。装完之后最好在证书管理器里搜一下,确认能看到这张证书,位置在受信任的根证书颁发机构下面。
macOS 的流程不一样,需要通过钥匙串访问。打开钥匙串访问,把 crt 文件拖进系统钥匙串,找到这张证书,双击打开信任设置,把"使用此证书时"改成"始终信任"。新版 macOS 对根证书的管理更严格,改完可能需要输入密码确认。
Firefox 是最特殊的一个,它有自己的证书库,不读系统证书。所以如果你用 Firefox 录制,必须单独在 Firefox 的设置里,找到隐私与安全,往下拉到底部的证书管理,选查看证书,在证书颁发机构那一栏点导入,把 crt 导进去,并且勾选信任这一项用于标识网站。这一点很多人不知道,装完系统证书之后用 Chrome 能录,换 Firefox 就报证书错误,问题就出在这。
浏览器代理的配置有两种方式。一种是改系统代理,Windows 在设置里找代理,macOS 在网络的代理选项里配,填上 127.0.0.1 和录制器的端口。另一种是用浏览器上的代理切换扩展,配置更灵活,可以一键开关。我个人更推荐后一种,因为录完之后点一下就能关掉,不容易忘。
说到忘,这是最经典的一个坑:录完脚本忘了关代理,然后关掉 Jmeter,浏览器就再也上不了网了,所有流量都往一个已经关闭的端口上撞。遇到这种情况不用慌,把代理关掉或者改回直接连接就好了。
4.3 过滤器配置:把静态资源和第三方域名挡在门外
不加过滤直接录,结果一定是一堆垃圾。一个后台首页加载下来,图片、样式、字体、图标字体、脚本文件加起来能有三四十个请求,这些对压测来说毫无价值,白白撑大脚本体积,还会严重干扰你阅读请求序列。
录制器里的 URL Patterns to Include 和 URL Patterns to Exclude 就是干这个的。Exclude 里用正则把静态资源的后缀统统挡掉,常见写法是把图片、样式、脚本、字体、图标、源码映射这些扩展名全部列进去。这个正则写一次就能复用很久,建议存下来。
Include 那一栏通常用来限定只录目标域名。如果被测系统会调用很多第三方服务,比如统计埋点、短信验证码、地图服务,把这些域名排除掉非常关键。否则你录出来的脚本里会混进一堆外部请求,回放的时候还要依赖外网,压测结果会被这些无关链路污染。
我的习惯是分两遍录。第一遍不加任何过滤,全量录一遍,然后打开请求树看看都经过了哪些域名,把不认识的、跟业务无关的挑出来。第二遍加上过滤规则重新录,这时候出来的脚本就干净多了。多花五分钟,省后面两小时。
4.4 录完之后的四步清理与初步关联
录完的脚本别急着压,先做四件事。
第一步删冗余。把静态资源、埋点、心跳、favicon 这些跟业务无关的采样器全删掉。删的时候注意别把 Cookie 管理器和 HTTP 信息头管理器连带删了,这两个是基础元件,得留着。
第二步理结构。按业务动作把采样器组织到事务控制器下面,比如登录、浏览商品、加购、下单、支付各自成组。之后看聚合报告的时候就能按事务维度看响应时间,比一个个采样器看有意义得多。
第三步处理关联。这一步是最花时间的。脚本里所有会话相关的动态值,比如登录后返回的令牌、会话标识、页面里藏着的校验字段,录制下来的时候都是固定值,回放必挂。需要加后置处理器去动态提取,再用变量引用。第 7 节会专门讲提取器的用法和那个特别有名的防伪标记案例。
第四步做初步参数化。把账号密码、商品编号这类会变的输入换成变量,为后面接 CSV 数据文件做准备。这一步不做,多线程跑起来所有用户都用同一个账号,服务端要么串数据,要么直接把并发锁死,压出来的结果完全没有参考价值。
注意:录制期间不要同时开着别的抓包工具或者代理插件。两个代理抢同一个端口会失败,更麻烦的是流量被另一个工具截走了,Jmeter 这边录出来是空的,你还以为是配置写错了。
5. 方式三:抓包导出 HAR / JMX,移动端和复杂场景的主力
5.1 什么时候该放弃浏览器代理,改用抓包
浏览器代理录制的边界在哪里?在浏览器之外。一旦被测对象变成手机 App、桌面客户端,或者小程序里的某些原生请求,浏览器代理就使不上劲了,因为流量根本不从浏览器走。这时候抓包工具就是必选项。
还有一种情况是只需要录某个特定接口。比如你只想压一个查询接口,但是触发它的页面会连带发几十个请求,用代理录制再删筛选,效率很低。用抓包工具可以精确地只挑那一条会话导出,省事很多。
第三种情况是团队已经在用抓包工具做接口测试,抓包结果可以复用。这时候直接在已有的抓包流程上导出脚本,比重新搭一套代理环境要顺畅。
我最常用的一招其实是浏览器开发者工具。打开开发者工具的 Network 面板,勾上只显示异步请求,把业务操作走一遍,然后在请求列表上右键,选择保存所有内容为 HAR 格式。整个过程不需要装任何额外软件,导出的 HAR 文件里包含了完整的请求头、请求体、响应信息,是后续转换的原料。
5.2 从 DevTools/Fiddler 到 JMX 的两条转换路径
从抓包结果到可执行的 Jmeter 脚本,有两条路。
第一条路是用抓包工具直接导出 JMX。部分抓包工具在会话导出菜单里就提供了 Jmeter 脚本这一项,选中你要的会话,导出成 .jmx,用 Jmeter 打开即可。这条路的好处是省掉了中间格式转换,缺点是导出的脚本元件结构比较朴素,头信息管理器、Cookie 管理器这些基础元件往往没有自动带上,需要你打开之后手动补。
第二条路是走 HAR 中转。抓包工具和开发者工具都能导出 HAR 格式,然后借助转换工具把 HAR 转成 JMX。社区里有若干开源的转换工具,有命令行的也有图形界面的,用法基本都是输入 HAR 输出 JMX 这个套路,选一个还在维护的版本就行。命令行工具的一般用法类似下面这样。
# HAR 转 JMX 的命令行套路,具体工具名按你选的那个来 java -jar har-to-jmx.jar -i capture.har -o script.jmx # 转换完用非 GUI 模式跑一遍冒烟,看结构有没有问题 jmeter -n -t script.jmx -l smoke.jtl走 HAR 这条路有几个细节要注意。HAR 文件会把每个请求的响应体也塞进去,一个完整的业务流程 HAR 可能有几十上百兆,转换过程会很慢。所以导出前先在开发者工具里用筛选条件只留下你要的请求类型,能小一个数量级。另外 HAR 里记录了每个请求的耗时,转换工具有时候会把这些耗时翻译成定时器,压测的时候会拖慢整个脚本,转换完记得检查并清掉。
5.3 手机 App 抓包的操作顺序与合规边界
手机端抓包的顺序固定,一步一步来基本不会出问题。手机和电脑连同一个无线网络,确认两边在同一个网段,能互相访问。在电脑上启动录制器或者抓包工具,看清它的监听端口。然后在手机的无线网络设置里,把当前这个网络改成手动代理,主机名填电脑的局域网地址,端口填工具监听的端口。保存之后打开 App 走业务流程,电脑这边就能看到请求。
证书这一关躲不掉。手机浏览器访问代理地址,下载并安装根证书,安卓系统需要在安全设置里手动安装到用户证书,部分高版本系统还需要在应用层面允许用户证书,iOS 则要在设置里安装描述文件之后再手动开启完全信任。这几步任何一步漏了,看到的就全是加密流量,只有连接记录没有内容。
这里必须说清楚一条边界。如果 App 对证书做了绑定,代理方式拿到的流量会直接中断,请求发不出去。遇到这种情况,正规做法是找开发同学要一个测试包,或者直接要接口文档自己手写,而不是去折腾各种绕过手段。压测的目的是验证自己系统的承载能力,不是在突破安全机制,走正门最快也最安全。
6. 方式四:浏览器扩展录制,五分钟出脚本的快路子
6.1 扩展录制的工作机制与天然优势
浏览器扩展录制的原理跟前三种都不一样。它不去架代理、不去解流量,而是直接读浏览器自己的网络事件。扩展在浏览器里跑,浏览器发出什么请求它就知道什么请求,把它记成一条条步骤,最后导出成 JMX。这个过程天然绕开了证书和代理这两个大麻烦,因为流量根本没被截走,走的还是浏览器自己的安全通道。
这个特性带来三个明显的好处。第一是零配置,装完扩展点一下录制就能开工,对刚接触性能测试的人来说心理门槛低很多。第二是 HTTPS 无痛,不用装根证书、不用配代理,也不会有忘关代理然后上不了网的问题。第三是操作直观,扩展界面上一边操作一边能看到已经被记录的请求列表,录错了当场就能发现。
但它也有明确的短板。扩展只能看到浏览器发的流量,手机上运行的原生应用、桌面客户端、后台服务之间的调用,它一概看不见。另外某些单页应用的请求是在页面生命周期里大量异步发出的,扩展可能只记录到一部分,或者把发起顺序记乱了。还有一个隐性问题:扩展录制的是浏览器视角的请求,有些 Cookie 和头信息是浏览器自动补的,导出的脚本里不一定带,回放的时候可能因为缺少会话信息而失败。
6.2 导出的 JMX 打不开?兼容性问题的处理
用扩展录出来的脚本,最常见的报错是打开的时候提示找不到某个类。原因很简单,这类扩展通常自带一套元件实现,导出的 JMX 里引用了它们自己的组件类,而你的 Jmeter 里没有这些类。解决办法有两条。
一条是补依赖。用插件管理器搜索并安装扩展所依赖的插件包,装完重启 Jmeter,报错就消失了。大多数主流录制扩展都会在导出页面或者文档里说明需要哪些插件,按图索骥就行。
另一条是清理引用。如果实在找不到对应插件,或者那个插件已经停止维护了,可以另存一份脚本备份,然后用文本编辑器打开 JMX 文件,搜索里面引用到的类名,把不认识的元件连同它的配置一起删掉,只保留标准的 HTTP Request 采样器和控制器。这个操作有点糙,但保住主要请求链条是没问题的,实测下来能救回八九成的脚本。
还有一个更省事的做法:导出的脚本只当草稿用,把所有请求结构复制到一个自己新建的干净测试计划里,重新挂上标准的 HTTP 信息头管理器、HTTP Cookie 管理器、CSV 数据文件设置。这样结构干净,也不会带着别人家的依赖,后面维护起来省心。
7. 录制只是骨架:把脚本改造成真正能扛压的脚本
7.1 参数化:CSV 分配模式与 CSV/JDBC 数据源的用法
录制出来的脚本,输入值全是写死的。多线程一跑,所有线程拿同一个账号登录、查同一个订单,服务端要么直接把你踢掉,要么数据串成一片,压出来的数字毫无意义。参数化就是把写死的值变成可替换的变量。
最常用的是 CSV 数据文件设置。配置项里几个字段必须搞明白。文件名填相对或绝对路径,编码一定要写 UTF-8,不然中文数据读进去是乱码。变量名是按列顺序用逗号分隔的,多个变量对应多列。分隔符默认是逗号,如果你的数据里有逗号,记得换一个分隔符或者勾上允许带引号的数据。
真正决定行为的是最后两个选项:文件读到结尾之后是否循环、以及是否停止线程,还有那个共享模式下拉框。这三项的组合决定了数据怎么分到各个线程手里。
| 共享模式 | 行为 | 适用场景 |
|---|---|---|
| 所有线程共享 | 所有线程共用一个文件指针,每个线程每轮取到不同的行 | 唯一账号登录,一个账号只用一次 |
| 当前线程组 | 每个线程组内部共享一个指针 | 多线程组并行压不同业务 |
| 每个线程独立 | 每个线程各自从头开始读文件 | 每线程固定绑定一套账号 |
如果你想要"同一个 CSV 文件里每个线程分块取值",本质上是想让各线程拿到的数据互不重复。实现方式是选所有线程共享,同时把读完是否循环关掉、读到头是否停止线程打开,这样文件从头到尾被消费一遍,线程之间自然不重叠。如果数据量不够,还不如按线程拆成多个文件,用线程号拼文件路径,每个线程读自己那份,边界最清楚。
数据库取值是另一条常见路子。用 JDBC 连接配置建好连接,然后用 JDBC 请求元件执行查询,查询结果会自动按列生成变量,引用的时候加上索引,比如第一行第一列就是变量名加下划线加一。想让查询出来的数据作为下一个接口的入参,有两种常见做法:一种是在后续请求里直接引用带索引的变量,针对固定行;另一种是配合循环控制器遍历整个结果集,把每行都跑一遍。后一种更适合批量场景。
-- JDBC 请求里的查询语句,变量名填 orderId,orderNo,对应两列 SELECT id AS orderId, order_no AS orderNo FROM t_order WHERE status = 0 LIMIT 100;配置 JDBC 连接的时候有个容易忘的点:数据库驱动 jar 包要放到 lib 目录下,放完重启 Jmeter。连接串、用户名、密码建议用变量引用,不要把生产密码明文写在脚本里,脚本是要提交到版本库的。
7.2 关联:正则、JSON 提取与防伪标记的经典案例
关联解决的是"上一个请求的响应里有下一个请求需要的值"这个问题。做法是在产生这个值的采样器下面挂一个后置处理器,把值提取出来存成变量,后面的请求用变量引用。
提取器有两类最常用。一类是正则表达式提取器,靠引用名称、正则表达式、模板、匹配数字这几个字段工作,万能但对嵌套结构不友好。另一类是 JSON 提取器,直接按 JSON 路径表达式取值,写起来简洁,前提是响应确实是 JSON。能用 JSON 提取器就别用正则,出错的概率低很多。
这里说一个特别有代表性的坑。有些基于老式 Web 框架的系统,表单提交的时候会带一个防伪标记字段,页面上以隐藏输入框的形式存在,值每次刷新都变。你用录制的方式把这个值抄下来,回放的时候服务端一校验,直接返回"未提供必要的防伪标记",接口 500 或者 400。这个问题的本质就是典型的关联缺失。
解决办法是三步。第一步,在打开表单页的请求下面加一个提取器,用 XPath 或者正则把这个隐藏输入框的 value 取出来,存成变量。第二步,在提交表单的请求里,把原来写死的值改成变量引用。第三步,确认 Cookie 管理器是开着的,因为一般还会有一个会话 Cookie 需要一起带上。
签名类参数是关联的另一个极端。有些接口的请求参数里带时间戳和签名,签名是密钥加时间戳拼起来算的哈希。这种值没法从响应里提取,必须用脚本预处理器现场算。做法是在请求上加一个 JSR223 预处理器,用 Groovy 算出签名塞进变量里。如果签名算法特别复杂,还有一个很实际的办法:找开发同学在测试环境把签名校验开关关掉。压测的目的是测承载能力,不是测签名算法,能在测试环境省掉的校验就别硬啃。
// JSR223 预处理器示例:拼一个带时间戳的签名变量 def ts = System.currentTimeMillis().toString() def raw = "appKey=demo&ts=" + ts + "&secret=testsecret" def sign = raw.encodeAsMD5() // 按实际接口的算法替换 vars.put("ts", ts) vars.put("sign", sign)7.3 断言、事务控制器与定时器:让压测结果站得住
没有断言的压测是假压测。默认情况下,Jmeter 只看 HTTP 状态码,返回 200 就算成功。可很多系统出错的时候也返回 200,错误信息藏在响应体里。这种情况下你压出来的成功率百分之百,实际业务全线失败,报告拿出去是要出事的。
断言要分层加。最外层加一个响应断言,检查响应里是否包含成功标识;再往细里加 JSON 断言,按字段判断业务码。如果判断逻辑比较复杂,就用 JSR223 断言写脚本判断,灵活性最高。
// JSR223 断言示例:按业务码判断请求是否真的成功 def resp = prev.getResponseDataAsString() def json = new groovy.json.JsonSlurper().parseText(resp) if (json.code != 0) { prev.setSuccessful(false) prev.setResponseMessage("业务码异常: " + json.code) }事务控制器是把一组采样器的耗时合并成一个事务指标显示的元件。勾上生成父采样器之后,报告里就能看到"下单"这个动作端到端的响应时间,而不是分散在七八个接口上。还有一个细节:是否把定时器和前后置处理器的耗时算进事务,这个选项要按你的统计口径来选,跟团队说清楚,否则同一份报告不同人算出来的数不一样。
定时器决定了压测的形态。想模拟真实用户,就在请求之间加一个随机等待,模拟人的思考间隔。想按固定吞吐量加压,就用恒定吞吐量定时器,设定每分钟的请求数目标,Jmeter 会自动调节请求间隔去逼近这个目标。这是做阶梯加压和容量摸底时最有用的一个元件。定时器的作用域要注意,放在线程组下面它会影响组内所有采样器,放在某个采样器下面只影响那一个。
最后是结果收集。察看结果树在调试期很有用,可以看到每个请求的完整请求响应内容,但它非常吃内存,正式压测时必须关掉。压测用非 GUI 模式输出结果文件,再用命令生成网页版报告。如果确实需要从结果树里导出某段数据排查问题,用界面上的保存表格数据功能导出成 CSV,但别在整个压测过程中一直开着这个监听器。
8. 录制与加压阶段的高频问题速查
8.1 录制阶段:证书、空脚本、代理失效
录制环节的问题集中度高,基本就那几个,整理成表随时翻。
| 现象 | 常见原因 | 处理办法 |
|---|---|---|
| 开录之后浏览器打不开任何网页 | 代理指向了没启动的端口,或 Jmeter 已关闭 | 关掉浏览器代理,重启录制器,再开代理 |
| 页面能开但录不到内容 | 只录到一条连接请求,证书没被信任 | 重新生成根证书并安装到系统或浏览器证书库 |
| 用 Firefox 录 HTTPS 报证书错误 | Firefox 有独立证书库,不读系统证书 | 在 Firefox 证书管理里单独导入并设为信任 |
| 录出来的脚本全是图片和样式 | 过滤规则没配 | 在排除规则里按后缀正则挡掉静态资源 |
| 脚本里混了一堆外部域名请求 | 埋点、统计、第三方服务被一起录了 | 把这些域名加进排除规则,或者录完手动删除 |
| 回放全部失败,提示会话失效 | 动态值没做关联,或缺 Cookie 管理器 | 加提取器动态取值,补上 Cookie 管理器 |
| 表单提交报缺少校验标记 | 页面里的隐藏校验字段是动态生成的 | 从表单页响应中用提取器取值,提交时引用变量 |
| bin 目录里找不到证书文件 | 解压路径含中文、空格,或无写权限 | 换成纯英文短路径重新解压 |
| 手机端抓到的全是加密流量 | 证书没装到用户证书,或未开启完全信任 | 按系统要求安装描述文件并开启信任开关 |
还有一条不算问题但容易被忽略的经验:录制环境要尽量贴近真实。用测试环境的数据和账号,走完整的业务链路,别图省事跳步骤。录的时候少走一步,压测的时候就少覆盖一个接口,最后报告里的容量数字就是虚的。
8.2 加压阶段:报错、端口耗尽与压测机瓶颈
录好脚本、调好参数,正式加压的时候还有一波坑在等你。这些坑跟录制本身没关系,但每一步都在消耗你的时间,所以一并列出来。
| 现象 | 常见原因 | 处理办法 |
|---|---|---|
| 大量请求报写入服务器失败 | 服务端主动断连,或上传文件过大,或连接被限流 | 降并发压测定位临界点,调大超时,检查网关连接数限制 |
| 请求报连接被重置 | 中间层并发保护,或长短连接不匹配 | 调整连接复用策略,与服务端配置对齐 |
| 内存溢出 | 结果树开着,或堆内存太小 | 关监听器,调大堆,用非 GUI 模式跑 |
| 压测机端口不够用 | 短连接太多,本机动态端口被耗尽 | 开启连接复用,调整系统连接参数,增加压测机 |
| 压测机 CPU 或网卡先打满 | 压力发不出去,瓶颈在客户端 | 用系统监控确认,把压力分散到多台机器 |
| 并发加不上去,TPS 卡在低位 | 定时器限制、重定向过多、或有同步等待 | 检查定时器作用域,排查重定向和阻塞点 |
| 结果文件几十个 G | 把响应数据一起存了 | 关闭响应数据保存,只留统计字段 |
| 结果树导出卡死 | 边压边用界面导出 | 用非 GUI 模式输出结果文件,事后生成报告 |
关于"写入服务器失败"这一类报错,我的排查顺序是固定的:先降并发,把线程数压到很低再跑一遍,如果低并发正常那就是承载问题;如果低并发也报,就是脚本或协议层面的问题,去检查是不是请求体太大、是不是缺少必要的头、是不是服务端对某个接口有特殊限制。这个二分法能快速把问题圈定在"容量"还是"脚本"这两个大类里,比盲目改参数高效得多。
分布式的思路也值得早一点考虑。单台压测机的并发能力其实很有限,几千并发往往就已经被本机端口和网络带宽卡住了。早期就用多台压力机做分布式,能避免"以为是服务端扛不住,结果是压测机先趴了"这种误判。用命令行模式启动从节点和控制节点,把线程数分摊下去,结果汇总到一个文件里分析,这是做高并发验证的标准姿势。
我个人在实际操作中的体会是,录制这件事真正的价值不在录,而在录完之后你被迫去理解整个业务链路。你得一层层剥开请求,看清楚哪一步在传会话、哪一步在算签名、哪一步的结果被下一步消费,这个过程中你对系统的理解会远超写脚本本身。所以别把录制当捷径,把它当一次把系统调用链画清楚的机会,压测报告的每一个数字,才站得住脚。