☰
JMeter JSR223取样器实战:动态参数与高并发压测核心技巧
2026/10/2 9:43:50 网站建设 项目流程

如果今年我只能给 JMeter 新手推荐一个取样器,我一定选 JSR223。原因很简单:压测场景里那些“卡脖子”的动态参数、复杂断言、数据加工,最后基本都是靠它兜底的。这套《Jmeter取样器之JSR223取样器详解》系列能写到第 7 篇,也说明这个取样器值得反复啃。

上个月我刚做完一次真实的迁移后压测验证:单节点 k8s 上的若依微服务整套环境,要求准不停服、不丢数据地迁移到阿里云 ECS。迁移完成后,压测同事用一套配套的 JMeter 脚本跑高并发,验证云上环境到底能扛住多少流量。这套脚本里最有含金量的部分,恰恰都落在 JSR223 取样器上。所以这一篇,我结合这次实战,把 JSR223 取样器的用法、内置变量、参数化思路、排查技巧一次讲透。

无论是刚接触 JMeter 的小白,还是已经在做接口测试、性能测试的工程师,这篇文章都能让你少踩几个坑。

1. JSR223取样器到底怎么用才不拖垮压测性能

1.1 为什么压测脚本里JSR223是主角

很多人一开始做压测,习惯用 HTTP 请求默认值加一堆“录制回放”的脚本。这种思路对付纯静态页面还行,一旦涉及真实业务就崩。比如若依这种前后端分离的微服务项目,接口之间有严格的登录态校验、权限控制、动态 Token,有的接口还需要对时间戳和参数做签名,纯录制回放根本走不通。

JSR223 取样器厉害在哪?它允许你在取样器内部执行一段脚本,这段脚本可以是 Groovy、JavaScript、Beanshell(虽然不建议)、Python 等 JVM 上能跑的语言。脚本可以访问 JMeter 的上下文,能动态生成请求参数、修改请求体、读取响应结果、控制逻辑流程、写日志。相当于把一个原本只能发固定请求的“机械臂”,升级成了有逻辑处理能力的“机器人”。

在这次若依迁移压测里,最核心的场景就是登录拿 Token 然后访问业务接口。若依登录成功后会返回加密后的 Token,后续所有请求都要在 Header 里带上这个 Token。如果只是录制脚本,Token 是固定死的,压测跑几分钟就失效,后面的请求全是 401。用 JSR223 取样器,就可以在每轮迭代或每个虚拟用户开始时动态请求登录接口,解析出 Token 并传给后续请求,这样压测才能真正模拟真实用户行为。

1.2 JSR223和BeanShell怎么选

JMeter 里有个老牌的脚本取样器叫 BeanShell,很多老教程都在用。但如果你要压测高并发,我真的劝你尽早放弃 BeanShell,换 JSR223 + Groovy。

我第一次用 BeanShell 写脚本时觉得挺方便,直到有一次压测 200 并发,施压机自己的 CPU 直接打满,报错全是一堆等待超时。后来才发现,BeanShell 每一次执行都要启动脚本解释器,性能开销特别大。JMeter 官方从 3.1 版本开始就明确建议使用 JSR223 + Groovy,并且强调要开启脚本缓存。

JSR223 的优势体现在两个地方:第一,它基于标准 Java Scripting API,与 JMeter 集成更好;第二,Groovy 脚本可以编译缓存,只要脚本内容不变,后续执行直接复用编译结果,性能比 BeanShell 高一个数量级以上。

实际选型时,我的建议很直接:

  • 能用 Groovy 别用 Beanshell,除非你维护的旧脚本里全是 Beanshell,短期内不打算重写。
  • 只做简单参数赋值,用 JMeter 自带的函数也好,没必要上脚本。
  • 一旦涉及循环、条件判断、字符串处理、正则提取、JSON解析,直接上 JSR223。

另外,在 JDK 9 以上的环境里,BeanShell 偶尔会出现奇怪的 ClassNotFound 异常,排查起来非常痛苦。Groovy 虽然在类加载上偶尔也有坑,但整体可靠太多。

2. JSR223取样器核心概念与内置变量解析

2.1 界面参数与常用语言配置

JSR223 取样器的界面看起来简单,但里面有几个配置点很容易被忽略。逐个说:

  • 名称:写清楚这个取样器在干什么,比如“JSR223-登录并提取Token”。压测脚本一多,命名清晰能省很多时间。
  • 语言:建议选 Groovy。JMeter 插件包里自带 Groovy 支持,不用额外装环境。
  • 参数:这里可以传递一些静态或半静态参数给脚本。比如在“参数”框里写username=admin password=123456,脚本里可以用Parameters这个变量接收,再自行解析。
  • 脚本:真正写逻辑的地方。
  • 缓存编译脚本:这个选项一定要勾上。勾选后,脚本内容不变时,JMeter 不会重复编译。我在压测时经常发现,没勾缓存时线程数一高,引擎线程全卡在编译上。勾上之后性能立刻稳定下来。

另外,JMeter 5.x 里还有一个隐藏坑:如果你在脚本里调用了外部 jar 包里的类,首次运行大概率会报类找不到。原因不是你的 jar 没放对,而是 Groovy 脚本的类加载器在缓存模式下不会自动刷新。解决办法是去掉缓存编译,或者把 jar 放到 JMeter 的 lib/ext 目录后重启 JMeter,保证类能被正确加载。

2.2 vars、props、ctx、prev四个内置对象怎么用

JSR223 脚本最核心的就是四个内置对象,初学阶段把这四个对象玩明白,脚本就入门了。

vars是当前线程的变量容器,可以把它理解成每个虚拟用户自己的小本子。vars.put("token", "abc")存一个值,vars.get("token")取一个值。注意:这里是字符串,取值出来永远是 String。如果要在脚本里做计算,用vars.get("count").toInteger()之类的转换。

props是全局属性容器,所有线程共享,可以理解为 JMeter 进程级别的公共黑板。常见的用途是统计全局错误数、保存开关状态。举个例子,你可以用props.put("errorCount", count)在多个线程里累加错误,最后在 JSR223 断言或后置处理器里读出来。

ctx是 JMeterContext 对象,能拿到当前线程组、当前取样器、当前变量等完整上下文。这个对象平时用得不多,但在做复杂逻辑控制、动态修改取样器属性时非常有用。

prev是上一个取样器的 SampleResult,也就是取样器的执行结果。你可以通过prev.getResponseCode()拿到 HTTP 状态码,prev.getResponseDataAsString()拿到响应体字符串,prev.getTime()拿到耗时。这个对象是做断言、提取数据的核心入口。

写一个最常见的登录提取代码示例:

import groovy.json.JsonSlurper // 获取前一个HTTP取样器返回的响应体 def response = prev.getResponseDataAsString() def json = new JsonSlurper().parseText(response) // 假设响应体结构是 {"code":200,"token":"xxxx"} if (json.code == 200) { vars.put("token", json.token) log.info("Token获取成功: " + json.token) } else { log.warn("登录失败,响应: " + response) }

这段代码就是若依迁移压测脚本里登录逻辑的核心。响应体解析用 JsonSlurper,比正则提取可靠得多,而且性能不错。

2.3 动态参数和加签逻辑怎么落地

压测时最烦的就是动态参数。比如有的接口要求请求体里必须带一个timestamp,还得把 URL 参数按字典序排序后拼接一个sign字段。用 JMeter 自带函数处理这种逻辑非常痛苦,因为你没法在一个表达式里做排序和拼接。但 JSR223 里就是用 Groovy 写一段代码的事。

我在若依的压测脚本里就写过一个签名工具类逻辑,放在 JSR223 预处理脚本里。先通过vars拿到请求参数,然后排序、拼接、MD5 加密,最后把签名结果放回vars,HTTP 请求里直接引用${sign}。整个流程 10 行代码以内搞定。

要注意的是:签名逻辑不要在每个请求里复制粘贴,而是应该封装成一个 Groovy 方法,或者放进 JSR223 的“初始化脚本”里统一加载。JMeter 的 JSR223 取样器支持“初始化脚本”选项,在脚本第一次执行前会执行一次初始化代码。把公共方法放在初始化脚本里,性能更好,代码也更干净。

3. 压测若依微服务:登录态、参数化与数据校验

3.1 登录拿Token的脚本写法

若依微服务的登录流程一般是:前端把用户名和密码提交到/login接口,后端校验成功后返回一个 Token。压测时要模拟大量用户,不能所有用户共用一个账号,否则后端会限流或封禁。我当时用的是 CSV 文件存放测试账号,每个线程读一行,动态登录。

具体实现分三步:

第一步,在测试计划里放一个 CSV Data Set Config,配置好账号文件的路径、变量名username、password。重点是设置“共享模式”为“当前线程组”,确保每个线程拿到的数据不重复,避免压测时并发获取到同一行数据。

第二步,添加一个 HTTP 取样器,发送 POST 请求到/login,请求体用${username}和${password}引用 CSV 里的账号。这里不需要硬编码任何东西。

第三步,在 HTTP 取样器后面加一个 JSR223 后置处理器,脚本里解析响应、提取 Token 并存入vars。后续所有业务请求的 Header 里都用${token}引用。

这里一个关键细节:若依项目如果开启了验证码,压测环境最好通过配置关闭验证码功能,否则脚本里还要处理 OCR,成本太高。如果没法关,可以在 JSR223 脚本里调用后端验证码生成接口,然后通过 OCR 识别,再把验证码值传给登录请求。我一般建议压测前就找开发把验证码关掉或者改成万能验证码,这是最省事的做法。

3.2 数据库查询结果参数化到下一个接口

压测过程中经常需要从数据库查出批量数据,比如一批用户 ID、订单 ID,然后把这些数据作为下一个接口的入参。这是 JDBC Request 参数化的经典场景,也是网上搜“jmeter将jdbc request查询出的数据作为下一个接口的参数”最常见的问题。

我在这次若依压测里,需要构造一批带真实业务数据的流程:先查数据库里已存在的项目 ID,再对每个项目 ID 发起详情查询请求。

推荐的做法有两种:

第一种是用 JDBC Request 配置查询 SQL,比如SELECT id FROM sys_project WHERE del_flag='0' LIMIT 100,在 JDBC Request 的“变量名”里填projectId。查询结果会生成形如projectId_1、projectId_2这样的变量,后面的请求可以用${projectId_1}引用。

这里有个大坑:JDBC Request 默认返回多行结果,但如果你不配置Result variable name(结果变量名),后面的 JSR223 脚本里根本拿不到完整的 List 结果。正确的做法是在 JDBC Request 的高级设置里填一个变量名,比如resultList,然后在 JSR223 脚本里这样取:

def rows = vars.getObject("resultList") rows.eachWithIndex { row, index -> // 每行row的类型是HashMap,可以用row.get("id")取字段 vars.put("projectId_" + index, row.get("id").toString()) }

第二种方式,直接在 JSR223 脚本里用 Groovy 连接数据库,查询数据并放入vars。这种方式更灵活,适合查询结果需要二次处理的场景。但要注意,脚本里写的数据库密码会暴露在脚本里,建议通过 JMeter 的props或环境变量读取,避免明文。

我自己更倾向于第一种方式,因为 JDBC Request 的配置可视化,填 SQL 和变量名比较直观,压测脚本其他人接手维护时也容易懂。

3.3 上传文件与唯一文件名处理

若依后台管理项目里的素材上传接口,是压测脚本里容易被忽略的难点。上传接口要求 multipart 请求里带一个文件,同时文件名不能和系统里已存在的重复。用 JMeter 的上传文件功能配合 CSV 参数化,可以准备一批不同的文件名,但问题是如果测试反复跑,第二次跑就会提示文件已存在。

解决思路是在 JSR223 脚本里动态生成一个唯一文件名。常见做法是用时间戳加随机数:

import java.text.SimpleDateFormat def timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) def random = new Random().nextInt(9999) vars.put("uploadFileName", "压测图片_" + timestamp + "_" + random + ".jpg")

然后在 HTTP 请求的文件上传表单里,文件名称栏填写${uploadFileName}。这样每次执行都会生成一个新文件名,不会冲突。

如果你还要校验上传后的文件确实存在,可以在上传请求后面加一个 JSR223 取样器,解析上传接口返回的 URL 或文件 ID,再用 HTTP 请求去访问这个资源,断言响应码是否为 200。这一步很重要,能验证文件是不是真的传上去了,而不只是请求发出去就算成功。

3.4 安全证书和HTTPS脚本录制问题

JSR223 和 HTTPS 本身不冲突,但我发现很多刚上手 JMeter 的同事,第一步就被证书卡住了。报错信息通常是“PKIX path building failed”或者“unable to find valid certification path”。如果你只是想做压测,不想深究证书体系,最简单的办法是把 JMeter 的jmeter.properties里这两行改掉:

server.rmi.ssl.disable=true httpclient.default.ssl.protocol=TLSv1.2

或者直接在测试计划里启用“Java”作为 HTTP 采样器实现,并把 HTTPS 证书校验关掉。我在真实验证云上环境时,都是通过 JMeter 选项里的“SSL Manager”导入服务端证书,或者让开发把测试环境的 HTTPS 临时改成 HTTP,压测完成后改回去。这里要注意,改协议会影响响应时间指标,所以压测报告里要注明当时用的是 HTTP 还是 HTTPS。

4. 高并发压测中的常见坑与排错实录

4.1 并发报告的数据怎么读才准

压测完成后,很多人第一件事就是看聚合报告里的 Average 响应时间。但 Average 很容易被长尾请求带偏,90 个请求都很快,10 个请求特别慢,平均下来数据就不好看了。我在这次若依迁移验证里,重点看的是三个指标:Throughput(每秒事务数)、Error%(错误率)、P95/P99(95%和99%的响应时间分位数)。

说个具体的:压测同事第一次跑完报告,Average 显示 800ms,看表面数据好像还能接受,但 P95 已经到了 2800ms,说明系统在长尾请求上性能很差。后来排查发现是应用服务器的数据库连接池太小,高峰并发时线程都在等连接。这个问题如果只看 Average,根本发现不了。

所以我的建议是:聚合报告里不仅要看平均响应时间,还要看 90% Line、95% Line、99% Line。JMeter 自带的聚合报告里有这些字段,直接用就行。如果想更直观,可以装 jp@gc 系列的响应时间百分位监听器。

4.2 常见异常速查表

压测脚本跑起来之后,报错主要集中在几个固定位置。我把自己踩过的坑整理成一张表,方便你直接对照排查。

报错信息常见原因排查方法
java.io.IOException: error writing to server客户端到服务端的连接被断开,通常是服务端主动关闭了 keep-alive 连接在 HTTP 请求里勾选“KeepAlive”,或减少单个线程的请求间隔时间
PKIX path building failedHTTPS 证书校验失败导入证书,或临时关闭 JMeter 的证书校验
__RequestVerificationToken 未提供必要的防伪标记某些 Web 框架要求请求头携带防伪 Token先用 JSR223 脚本 GET 登录页面提取 Token,再在 POST 请求 Header 里带上
提示文件已经存在上传文件名重复使用 JSR223 生成唯一文件名
同一个CSV参数化文件里每个线程重复取值CSV Data Set Config 的共享模式配置不对将共享模式从“所有线程”改为“当前线程组”
数据库参数化取值取不到JDBC Request 没有配置结果变量名在 JDBC Request 高级设置里填结果变量名
JMeter界面字体太小高分辨率屏幕下 JMeter 默认字体不够大修改 jmeter.properties 里的jmeter.hidpi.mode和字体大小参数

表格里这几类问题,几乎每一类我都亲自踩过。特别说一下error writing to server,这种报错在压测刚开始不出现,跑到一半突然大量冒出来,通常不是脚本问题,而是后端服务在压力下关闭了空闲连接。这时别急着改脚本,先看服务端的连接池配置和日志。

4.3 “不停服、不丢数据”迁移对压测脚本的要求

这次若依迁移后压测验证,和普通的压测有一点本质不同:业务要求准不停服、不丢数据。这意味着压测脚本不能只跑单个接口,也不能用静态数据把后端某些缓存全部打穿;它要尽量贴近真实用户行为。

所以我在脚本设计上做了三件事:

第一,构造完整业务流。压测路径是登录、查询列表、查看详情、提交表单、上传文件、退出登录,整条链路走完才算一个完整事务。这样做的好处是能验证迁移后各个模块之间的配合,而不是只验证某个接口的吞吐量。

第二,准备基础数据。高并发压测前,先从数据库准备一批真实可用的项目数据、账号数据。否则压测跑到一半,后端返回“数据不存在”,错误率看着很高,但其实是你的测试数据没造够。

第三,阶梯式施压。不要一上来就 100 个用户并发。我先从 10 个并发跑 2 分钟,看服务响应时间和错误率;然后逐步加到 30、50、80、100。每次加压后观察 1 分钟,确认稳定后再继续加。这种做法能帮你在压测过程中尽早发现性能拐点,而不是等全部跑完再分析结果。

还有一个细节:压测机的性能也会影响压测结果。如果单台机器跑不了高并发,一定要用分布式压测,否则漏报大量超时不说,压测结果也不能真实反映服务端承载能力。

4.4 JSR223脚本性能优化与常见写法误区

JSR223 脚本虽然强,但写不好反而会成为压测瓶颈。我见过不少同事在脚本里写了一个复杂正则,每次请求都重新编译一遍,导致施压机 CPU 直接爆掉。

几个核心优化原则:

  • 能用 JMeter 内置函数或后置处理器解决的事,不要全部写进 JSR223。比如简单的 JSON 提取,用 JSON Extractor 配置起来更快,性能也不差。
  • 脚本里避免创建大量临时对象,能复用就在初始化脚本里提前创建。
  • 避免在循环里使用vars.get()和vars.put()太频繁,因为每一次调用都有上下文切换成本。可以先把值取到局部变量,循环结束后再一次性写回vars。
  • 开启脚本缓存编译,这是最简单也最容易忽略的性能提升手段。

写法误区方面,最常见的是把 JSR223 取样器当成万能工具,一个脚本里塞了几百行代码,既不好维护,也拖慢执行。正确的思路是:一个 JSR223 取样器只负责一个明确的小任务,任务多了就拆成多个取样器,通过变量串联。

5. 我个人的经验体会

这套若依微服务从单节点 k8s 迁移到阿里云 ECS 的压测验证,前后跑了三轮才拿到稳定的报告。第一轮脚本写得太重,施压机自己先挂了;第二轮数据准备不足,错误率虚高;第三轮把 JSR223 脚本优化清楚、数据铺好、阶梯加压跑通,才算真正反映了云上环境的承载能力。

回头来看,JSR223 取样器不是万能的,但它在 JMeter 里确实扮演着“万能胶水”的角色。登录态管理靠它、签名逻辑靠它、数据库参数化靠它、复杂断言靠它,几乎每个关键环节都需要它。它也并不是越复杂越好,写得清晰、可维护、能复用,才是它最重要的价值。

如果你现在正准备用 JMeter 做压测,我建议你花一晚上把 JSR223 + Groovy 的内置对象过一遍,尤其是 vars、prev、ctx 的用法。剩下的问题,大多都能在跑一次真实业务压测后自己摸清楚。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询