十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

JMeter性能测试脚本录制与开发:从代理录制到分布式压测

JMeter性能测试脚本录制与开发:从代理录制到分布式压测 简介这是一份面向软件测试初学者的实验报告完整记录了基于LoadRunner对飞机订票系统开展性能测试脚本录制、增强与运行分析的实践过程。报告以Windows7和UTF应用软件为环境涵盖HTML-based与URL-based两种脚本录制方式对比、Step Navigator脚本查看与重命名、事务插入、用户名密码参数化、集合点设置、Controller运行分析以及Design Studio自动关联等关键操作同时配有大量界面截图、脚本代码和实验步骤说明便于读者按图索骥、理解性能测试脚本的开发思路。内容还包含订票流程综合应用、座位参数化、lr_out_message输出函数及扩展日志设置等进阶技巧能帮助学习者掌握处理动态数据、模拟并发用户和优化脚本效率的方法。压缩包内共1个doc文件大小6.2MB适合配合软件功能测试课程或LoadRunner自学使用已有419人学习浏览。1. 性能测试脚本不是录完就能跑录制和开发的分界线在哪实验5要交的报告叫“性能测试脚本录制和开发实验报告”但真正考验人的不是写报告而是能不能把“录制”和“开发”两件事分开。用工具自带的录制器抓出来的请求本质上只是浏览器操作的回放里面全是写死的用户名、时间戳和一次性 token换一台机器、换一个账号就失效。所谓“开发”就是把这份原始回放整理成能参数化、有关联、有断言的性能测试脚本。录制解决的是“哪些请求以什么顺序发出”开发解决的是“这些请求在并发场景下如何模拟真实用户”。两者之间有一条明确的分界线脚本能在单机环境稳定跑完一遍之后开发才刚过半。接下来以 JMeter 为工具从代理录制、参数化、关联、断言到分布式压测把这套流程说清楚。适合正在做性能测试课程设计的在校生也适合刚接手压测任务、想少踩坑的测试工程师。2. 用 JMeter 录制 HTTPS 脚本代理配置与证书处理2.1 录制前的测试计划结构录制不能直接打开 JMeter 就开始点。先建一个测试计划然后在计划下添加一个线程组再在线程组上右键选择“添加 → 逻辑控制器 → 录制控制器”。这个录制控制器会把代理服务器捕获到的采样器自动归拢到一起形成一棵树后续你可以在控制器上统一添加断言或后置处理器。如果不建录制控制器录制到的请求会平铺在线程组里一旦请求数量上百根本看不出登录、查询、退出之间的层级关系。线程组的线程数在录制阶段可以保持默认的 1因为录制只关心单用户路径是否完整。真正并发数在性能压测阶段才设置。录制控制器内部还要注意“暂停”属性这个属性决定代理是否自动插入思考时间。如果只想采集请求把暂停时间设为 0后面开发阶段再按需插入思考时间。合理的测试计划结构应该是测试计划 → 线程组 → 录制控制器 → HTTP 采样器。在设计测试计划时我习惯把录制控制器单独放在一个线程组里命名为“录制原始脚本”压测时再另建一个线程组把参数化后的采样器复制过去。这样原始录制脚本可以作为参照物一旦压测脚本调出了问题可以对比原始请求头和数据。实验报告里也可以贴出这个结构图能明显看出你懂脚本分层。2.2 HTTP(S) Test Script Recorder 的代理参数设置添加代理服务器的方式是测试计划 → 添加 → 非测试元件 → HTTP(S) Test Script Recorder。窗口打开后需要设置四个关键字段端口、目标控制器、分组、记录 HTTP 信息头。端口默认是 8080如果本机已经有服务占用 8080改成 8081 或 8888同时浏览器代理也要改为相同端口。目标控制器直接选择刚才建的录制控制器这样代理才知道把写入请求放到哪个节点。字段推荐值说明端口8080 或 8888必须与本机空闲端口一致目标控制器录制控制器右键下拉选择需要写入的节点分组每个组放入新的控制器按页面或事务切分请求后续好维护记录HTTP信息头勾选保留 Content-Type、Cookie、Authorization 等头分组选项里还有“每个分组放入新的控制器”和“无分组”。建议选“每个组放入新的控制器”这样 JMeter 会根据页面跳转分组但分组逻辑依赖响应时间和页面切换有时候会把异步请求拆到错误的分组。如果录制过程中发现请求顺序乱了也可以选“无分组”让所有请求平铺后面自己整理。“记录 HTTP 信息头”这个选项很重要。不勾选的话即使请求带了Authorization: Bearer录制时也会被丢弃回放必然失败。如果录制目标是 HTTPS 接口还要在 HTTPS 设置里确认“信任所有证书”已经打开否则建立代理链路时可能因为证书链不完整而失败。2.2.1 HTTPS 证书安装HTTPS 录制本质是中间人解密。JMeter 在首次启动录制服务器时会在bin目录下生成一个apachejmeter-temporary-rootca.crt根证书。浏览器如果不信任这个证书就会弹出安全警告甚至拒绝连接。安装证书的路径因系统而异Chrome 在chrome://settings/securityFirefox 在“设置 → 隐私与安全 → 证书 → 查看证书”中导入。导入时一定要选择“受信任的根证书颁发机构”存储区而不是“个人”存储区。证书安装后浏览器代理大概率就能记录到 HTTPS 流了。如果仍然出现SSL peer handshake failed先检查系统时间。证书校验对时间敏感系统时间偏差超过几分钟都可能握手失败。另外公司电脑常装有企业证书可能和 JMeter 临时证书冲突这时可以尝试把 JMeter 的监听端口改到 443 之外的任意端口然后重新启动录制服务器并再次导入证书。证书问题不是思路问题是链路细节实验报告里最好把证书导入前后的报错截图都保存一份。2.3 浏览器代理配置与录制步骤设置浏览器代理有两种常见做法。一是直接在系统设置里把 HTTP 和 HTTPS 代理指向127.0.0.1:8080这样所有浏览器流量都会经过 JMeter。好处是简单坏处是系统内其他软件的请求也会被录制脚本里会出现很多非业务请求。二是用 Chrome 启动参数创建独立的无痕窗口只让当前窗口走代理命令行如下chrome --user-data-dir/tmp/jmeter-profile --proxy-server127.0.0.1:8080用这条命令启动的 Chrome 窗口和日常浏览数据隔离代理配置也只对这个窗口生效录完即关不会污染本机网络环境。需要说明的是--user-data-dir是本次 Chrome 数据存储目录如果不指定Chrome 可能复用已有进程导致参数不生效。用这个技巧可以在录制和日常上网之间快速切换。启动代理服务器、配置好浏览器后按业务主链路操作一遍登录、查询列表、打开详情、提交数据、退出登录。操作之间留出可识别的停顿不要点太快否则 JMeter 无法准确区分事务边界。录制完成后回到 JMeter点击录制器上的“停止”按钮然后马上关闭浏览器窗口避免后续请求继续写入。此时录制控制器下会生成一批采样器建议先不要做任何修改直接在“查看结果树”里执行一次单线程回放看看有没有红色错误。2.4 录制结束后的脚本去噪录制出来的请求包里除了业务接口还有大量静态资源和埋点。静态资源如*.js、*.css、*.png在性能测试里通常不构成业务瓶颈应该过滤。过滤有两个层面一是在录制服务器上通过“排除模式”直接不录制二是在录制后手动删除。我的习惯是双保险在录制前把排除模式写全录制后按 URL 再扫一遍。常见的排除正则如下.*\.(js|css|bmp|png|jpe?g|gif|ico|woff2?)(\?.*)?这个正则可以搭配录制器“请求过滤 → 排除模式”使用。注意(\?.*)?是为了匹配文件名后带查询参数的静态资源比如/a.css?v123。如果不加这个后缀这类请求不会被过滤。录制完成后的去噪顺序是先按采样器名称排序把所有名字带.js、.css、.png的删除再找 URL 里含collect、track、metrics、report的上报接口这些接口由埋在页面里的第三方 SDK 触发和核心业务无关。实验报告里不能把这些请求留在脚本中否则会严重拉高吞吐量导致报告失真。去噪结束后把每个保留的采样器改成可读名称如“登录接口-提交凭证”、“订单列表-查询”后续聚合报告里显示的就是这些名称而不是一长串 URL。3. 脚本开发参数化、关联和断言让录制脚本变成可回归的资产录制脚本经过去噪后只是半成品接下来要处理的变量替换是开发的核心。开发的目标是让脚本在每一次运行时都能通过不同数据完成同一业务动作。先想清楚哪些值会变用户信息通常变订单时间一定变csrf token 和 session id 必须从服务器动态获取。把这些值抽象出来就是参数化从请求响应中拿值再交给下个请求就是关联。3.1 参数化的三种常用方式3.1.1 CSV 数据文件模拟多用户压测时不建议用固定账号否则所有压力都打在一个账号上缓存一旦生效测试结果会高得离谱。准备 CSV 文件是最常见的做法。先建一个users.csv内容格式如下username,password u_demo_01,Passw0rd#2024 u_demo_02,Passw0rd#2024在测试计划中添加“CSV 数据文件设置”配置文件名指向users.csv变量名填username,password分隔符用逗号。这里要理解 JMeter 的共享模式默认是“所有线程”意思是所有线程共享一个指针每取一行后指针后移一个迭代占用一行如果选择“当前线程组”每个线程组各自持有一个指针选择“当前线程”则每个线程独立读取适合需要每个线程都从第一行开始反复使用的场景。压测场景一般选“所有线程”因为这样最接近随机分配用户数据。CSV 文件的编码有一个大坑Windows 下使用记事本保存文件会默认带上 UTF-8 BOMJMeter 读取第一行时会把 BOM 拼进第一个变量名导致第一个请求的变量引用失败。常见表现是只有第一行数据出错后续数据正常。解决办法是把文件另存为 UTF-8 无 BOM 格式或者用 VSCode 重新保存。提示如果 CSV 文件里的密码包含${或,记得用引号包围字段在 JMeter 的 CSV 配置里同样有“允许带引号”选项默认是 False遇到特殊符号要打开。3.1.2 用户定义的变量如果某些参数在整个测试周期内都不变例如服务器 IP、端口、公共请求头里的Client-Id可以放在“配置元件 → 用户定义的变量”里。这个元件的变量在测试计划启动时初始化一次所有线程共享同一份副本。它和 CSV 的根本区别在于CSV 的数据是按迭代动态切换的而“用户定义的变量”是静态的。如果你把一个用户名放进去那么所有线程都会用这个名字压测就会退化成单用户重复提交。用户定义的变量适合做环境切换。把protocol、host、port定义好所有 HTTP 请求的路径写成http://${host}:${port}/api/xxx换环境时只需要改一处脚本不用动。这也是实验报告里体现工程化的一部分。不过要注意不要在变量名中使用 JMeter 的内置属性占位符否则会出现递归查找问题。3.1.3 函数助手 __Random对于流水号、手机号、订单号这类不参与业务校验的字段用__Random函数最省事。在需要随机值的位置直接写${__Random(10000,99999,orderId)}上面表达式的意思是生成 10000 到 99999 之间的随机数存到orderId变量中后续可以用${orderId}引用。这个函数在每次采样器执行时都会重新生成这可能带来一个问题同一个用户在一次登录流程中两个请求都要提交orderId而它们取到的随机值不一样。如果需要一次迭代内保持相同就要把函数放到“用户参数”里或者用setProperty做全局属性。最简单的变通是直接采用字符串拼接方式比如${__time(/1000,)}-${__Random(1000,9999)}生成一个包含时间戳和随机数的订单号。3.2 关联解决动态 Token 与 Session录制完成后你会发现某些响应里的 token 是服务器动态生成的比如登录后的Set-Cookie。如果脚本里把这些值写死回放第二次就会因为会话过期而失败。关联可以简单理解为“从前面的响应中提取变量再传给后面的请求”。零基础的同学第一次做往往不知道从哪找这个动态值可以在“查看结果树”里看第一个请求的响应数据复制 token 值然后在脚本里搜索这个值出现在哪些后续请求中从而确认关联点。3.2.1 用正则表达式提取器当响应是 HTML 时正则提取器是最容易落地的。比如登录页返回input typehidden name_token valuea1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6后续的下单请求必须带上这个_token。在登录请求上添加“后置处理器 → 正则表达式提取器”配置如下引用名称token 正则表达式name_token value([a-zA-Z0-9]{32}) 模板$1$ 匹配数字1逐项解释引用名称是后续引用时的变量名格式${token}。正则表达式的括号表示捕获组[a-zA-Z0-9]{32}匹配 32 位英文字母和数字$1$表示取第一个捕获组。匹配数字 1 的意思是取第 1 个匹配项因为 token 在页面中只出现一次。在提交请求的参数里把原来的_tokena1b2c3d4...直接替换为_token${token}。回放时登录请求的响应会先被提取器处理提取到值后下单请求再执行token 总是最新的。正则写起来要注意贪婪匹配。默认情况下.*会匹配到最后一个双引号容易把值截多。稳妥的做法是在待匹配值两侧写清楚边界左边是value右边是。如果你的响应里有两个 token就多写一个捕获组并调整“模板”为$2$。花一点时间在“查看结果树”里验证正则比瞎猜效果好得多。3.2.2 用 JSON Extractor现在大多数接口返回 JSON用 JSONPath 提取比正则直观。比如登录接口返回{ code: 0, data: { access_token: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiMTIzIn0.test } }在对应请求下添加“JSON 提取器”配置是引用名称填access_tokenJSONPath 表达式填$.data.access_token匹配数字填 1。JSONPath 的$.开头表示整个响应体data是下一级字段。这段表达式按层找字段比正则更抗格式变化。如果响应体是一个数组比如{data:[{token:aaa}]}则表达式需要写成$.data[0].token。注意数组下标从 0 开始。拿到 token 后后续请求的“HTTP 信息头管理器”里添加一项Authorization: Bearer ${access_token}。如果录制时信息头已经存在直接替换 value 即可。这里常见错误是 JSON 提取器配置了但变量始终为空原因大多是上一个请求已经失败自然没有响应可提。排查时先确保前置请求成功再检查 JSONPath 是否正确。3.3 断言与监听器配置写性能测试脚本必须加断言否则返回 200 的业务错误页会被当成成功。最常用的是“响应断言”配置在单个 HTTP 请求下面比如预期{code:0}或success: true。断言添加位置右键请求 → 添加 → 断言 → 响应断言。断言项配置位置常用值响应文本模式匹配规则包含字符串响应代码要测试的响应字段200响应信息忽略状态不勾选断言里的“模式匹配规则”默认是“包含”也就是只要响应文本里出现你填写的字符串就算通过。不要用“相等”因为响应体往往包含换行和时间戳完全相等很难满足。在一个流程中登录、提交、结果查询三个关键动作各加一个断言这样就能从错误率中快速定位是哪个环节失败。监听器方面调试阶段使用“查看结果树”压测阶段尽量去掉一切监听器。必要的话保留一个“聚合报告”但也要等到命令行跑完之后再打开。图形化监听器在 GUI 模式下会实时生成图表占用的内存可能超过脚本本身导致压测结果失真。如果实验报告需要截图可以在命令行结束后把 jtl 文件导入 GUI 再查看而不是边压测边开着监听器。3.4 调试技巧用 Debug Sampler 和 View Results Tree脚本写了参数化和关联后最头疼的是变量没有按预期取到值。添加“调试取样器”可以快速看到当前线程的所有变量。添加方式在采样器后面右键 → 添加 → 采样器 → 调试取样器。运行脚本后在“查看结果树”里点击“调试取样器”展开树形节点就能看到变量名和值。这个技巧比在代码里加日志直观得多适合零基础用户。调试取样器的输出会包含所有 JMeter 变量数据量很大。正式压测前必须删除否则每个线程每次迭代都会打印一大片变量内容到结果文件jtl 体积膨胀数十倍。另一个技巧是使用“JSR223 后置处理器”加一行log.info(vars.get(token))可以在日志中只输出一个变量避免刷屏。两种方式各有利弊初学者先用调试取样器跑通后再换成日志方式。4. 从单机脚本到分布式压测参数调整与常见瓶颈单机脚本跑通后实验的下一步是让脚本产生真实并发。这时要调的不是 URL而是线程组参数。很多新手把线程数调到 500结果自己本机卡死服务器却没多大压力。先理解线程数代表的是并发活跃线程不是连接数也不是 TPS。4.1 线程组参数设置三个必调参数是线程数、Ramp-up、循环次数。最简单的估算公式是线程数 目标 TPS × 平均响应时间秒。注意平均响应时间不是单个请求的而是整个事务的 P50 或 P90。比如测试目标是交易 TPS 100事务平均耗时 0.5 秒那么建议并发数就是 50。用 Python 计算如下target_tps 100 avg_response_time 0.5 # 秒 concurrent_users int(target_tps * avg_response_time) print(f建议线程数: {concurrent_users})Ramp-up 代表线程启动间隔。如果 50 个线程都设为 0就是瞬间全部发起可能会触发服务器的限流或 WAF 拦截。一般规则是让启动速率等于每秒新增 1-2 个线程也就是 Ramp-up 线程数 / 每秒启动速率。比如 50 个线程Ramp-up25 秒等价于每秒启动 2 个线程。这样做能看到从低到高的性能曲线而不是一个压力尖峰。参数作用推荐设置线程数并发用户数target_tps × 事务平均响应时间Ramp-up线程启动时间线程数 / 期望启动速率循环次数每线程迭代数压测时长足够时选“永远”循环次数和调度器“持续时间”是二选一。如果设置了持续时间 600 秒循环次数不会成为结束条件。实验报告里应该记录持续时间特别是需要分析稳定性场景的报告。另外线程组下面不要随意勾选“每次循环在独立线程组”之类的选项它会强制每个循环新建线程实际并发行为很难理解。4.2 脚本分层的两个场景前端性能测试与后端接口压测录制脚本包含静态资源请求但不同压测场景对这些请求的处理方式完全不同。做后端接口压测时脚本里只保留业务接口禁用“HTTP 缓存管理器”因为缓存会让服务器少算很多业务逻辑导致测出的 TPS 不是真实接口能力。而做前端整链路压测时要保留一部分静态资源并打开缓存管理器模拟老用户回访否则每个线程第一次访问都会拉取所有静态资源会明显高估服务器压力。这两种场景没有绝对的对错关键是在实验报告开头写明“本实验面向服务端接口能力因此过滤掉静态资源”。如果你不说别人看着脚本里有图片、有 CSS会质疑你的测试目的。另外事务控制器可以把多个采样器组合成一个逻辑事务比如“登录 查询 创建订单”作为一个事务聚合报告里只能看到这个事务的平均响应时间不会看到内部细节。这个做法在两种场景里都适用。4.3 回放时的高频报错与排查开发完脚本后回放最容易遇到三类错误。第一类是 401/403这是关联没生效或者是参数化的用户名没有对应权限。排查方式在“查看结果树”里点开失败的请求切到“请求体”标签看Authorization头和 Cookie 里的值是否和上一个请求响应中的提取值一致如果变量没有被替换${token}会原样出现在请求头里。第二类是 500多为业务参数错误比如订单号被随机函数生成了超出范围的值。第三类是连接超时常见于线程数开得很大本机文件描述符不够用。还有一个隐藏陷阱JMeter 默认每次请求都会新建 TCP 连接。在 HTTP 采样器的“高级”标签里把“使用 KeepAlive”保持勾选添加“HTTP 缓存管理器”可以复用连接降低压测机自身的连接开销。如果错误消息是Address already in use: connect说明本机端口耗尽需要调大系统临时端口范围而不是继续增加线程数。4.4 分布式压测的脚本一致性与远程启动单台压测机的线程数有限制超过 500 线程往往需要多台机器联合压测。JMeter 支持 master 和 slave 架构master 负责分发脚本和汇总结果slave 负责实际执行压力。分布式压测前必须保证脚本里的所有 CSV 数据文件、JAR 包、资源文件在每台 slave 机器上存在且路径一致。JMeter 不会自动同步这些依赖项这是分布式脚本开发最常见的坑。启动 slave 是在每一台机器上运行jmeter-server脚本然后 master 执行命令jmeter -n -t perf_test.jmx -R 192.168.1.10,192.168.1.11 -l result.jtl-R参数后面的 IP 列表是 slave 地址master 会把这些 slave 上的线程组加起来作为总压力。如果脚本中有用户变量和 CSV所有 slave 上的文件路径必须一模一样建议把数据文件放在 JMeter 安装目录的bin下并统一使用相对路径。分布式模式下的结果文件是汇总后的但每个 slave 的启动时间可能有数秒偏差分析报告时如果发现响应时间前段轻微分层可以先检查各 slave 的时钟同步用 NTP 同步时间。5. 脚本回放验证的四个步骤和一条命令行回归命令在提交实验报告前脚本必须经过一次可重复的回放验证。我的验证顺序是第一步单用户回放整个流程确认每个采样器都通过断言。第二步连续跑三轮小并发比如 5 个线程循环 3 次观察变量是否能在每次迭代中正确变化特别是 token 和 session 是否每次都不同。第三步对比两轮相同压测命令产出的 jtl 文件确认 TPS 和错误率波动在合理范围内如果波动超过 10%就要检查脚本里是否有随机函数引起的业务边界问题。第四步用命令行生成 HTML 报告留档。命令行回归命令是jmeter -n -t perf_test.jmx -l result.jtl -e -o /tmp/html-report简单拆解一下-n表示非 GUI 模式-t指定测试计划文件-l写结果文件-e生成 HTML 报告-o是输出目录。输出目录必须是尚不存在的路径否则 JMeter 会报Output directory is not empty。在实验报告里记录这条命令再加上执行前后的结果文件对比就能证明脚本具备可回归性。验证时还要注意一个反模式把“查看结果树”留在脚本里然后在 GUI 模式下跑压测。高线程数下结果树会把每个响应都缓存到内存用不了多久 JVM 就被塞满整个脚本假死。正确的做法是脚本里只放业务采样器和必要的断言所有监听器都删掉或者只在命令行结束时用-j参数捕获日志。想分析数据时把 jtl 文件导入 GUI 内的“聚合报告”查看。最后一步打开生成的 HTML 报告重点看Percentiles表和Response Time Overview图。如果 P95 明显高于 P50例如 P50 是 300msP95 是 1200ms说明存在明显的长尾请求。这时候不要急着加线程数先回到 jtl 里找出最慢的采样器名称再检查脚本里是否有同步导致的等待。响应时间的分布往往比平均值更有说服力把这一段对比写进实验报告能体现你理解性能测试而不只是会点“开始”按钮。本文还有配套的精品资源点击获取
返回列表