做性能测试,绕不开 LoadRunner 这个名字。即使现在开源压测工具已经非常成熟,LoadRunner 2022 R1 在很多商业项目里依然是默认选择:它把脚本录制、压力生成、资源监控、结果分析串成了一条完整链路,适合那些需要明确指标、可复现、好归档的场景。
我用它做过接口级脚本,也拿它做过整链路压测。这篇文章把最核心的概念和实操拆开讲:VuGen 怎么写脚本、Controller 怎么加压、Analysis 怎么读结果,以及我踩过的坑。无论你是刚接触性能测试,还是从 JMeter 切到 LoadRunner,都可以照着这套流程走一遍。
1. 先搞清楚:性能测试到底在解决什么问题
1.1 性能测试不只是“压一压看看崩不崩”
很多人第一次接触性能测试,脑子里就一句话:“用工具发起很多请求,看服务器扛不扛得住。”这句话没错,但不够完整。
性能测试的目标通常分几类:负载测试关心系统在预期业务量下响应时间是否达标;压力测试关心系统超过预期负载后表现如何,比如是否快速失败、恢复能力怎样;容量测试则是在做规划时回答“最多能支撑多少用户”;稳定性测试更关注长时间运行下是否有内存泄漏、连接池耗尽等慢慢暴露的问题。
举个例子:某个业务系统每天早上 9 点会迎来一波用户集中登录。如果你只做功能测试,你看到的是“一个人登录很快”,但只验证了正确性。性能测试要回答的是:当同时有 5000 个人在 5 分钟内登录,登录页会不会变成“龟速”,数据库连接池会不会被耗尽,应用服务器会不会假死。
我习惯用一个生活类比:功能测试是看“一个人过收费站能不能顺利通过”;性能测试是看“五一假期最后一天,收费站每个车道都排满车时,队排多长、通过率多高、哪个窗口先顶不住”。LoadRunner 就是那个帮你安排车流并发、记录每辆车耗时的工具。
1.2 LoadRunner 2022 R1 的四大组件是怎么分工的
很多新手拿到 LoadRunner 2022 R1,第一反应是“图标这么多,入口到底在哪里”。其实你只需要记住四个角色:VuGen、Controller、Analysis 和 Load Generator。
VuGen 负责“做脚本”。你录制或者手写脚本,定义用户会做什么操作、每一步怎么发请求、要不要做参数化、要不要关联动态数据。脚本做完之后,才算有了一个可复用的“虚拟用户”。
Controller 负责“组织并发”。你在 Controller 里决定启动多少个虚拟用户、花多长时间加载、持续跑多久、怎么停止。它还负责连接 Load Generator 机器,把脚本分发到一台或多台机器上执行。
Load Generator 是真正“干活”的机器。Controller 只做调度,压力是从 Load Generator 发出去的。这意味着你的压测机不是只有一台电脑,而是可以横向扩展到多台机器,避免“压测工具这边先撑不住”。
Analysis 负责“出结果”。压力跑完后,Analysis 会生成摘要、事务响应时间、TPS、吞吐量等图表。真正有价值的分析往往不是第一眼看到的“平均响应时间”,而是曲线变化趋势、错误分布、资源使用率之间的关联。
1.3 为什么有些团队还在用 LoadRunner
做技术选型时总有人问:JMeter 免费,LoadRunner 要授权,为什么还要用它?
我的看法是:LoadRunner 的价值不在某个单一功能上,而在于整套流程的成熟度。它提供的协议录制、脚本回放、参数化、关联、场景调度、资源监控是成体系的,而且对团队协作更友好。比如需求方要看“每秒事务数是多少”,你不需要和开发讨论脚本里哪个 sampler 对应哪个事务,直接在 Analysis 里导一份报告就行。
LoadRunner 2022 R1 的 VuGen 是 64 位版本,大脚本回放更稳定;录制和回放过程中的报错信息也更接近业务语言,方便定位是脚本问题还是服务端问题。如果你的团队预算充足,而且被测对象是复杂的企业级系统,用它做基线测试和准入测试是省心的选择。当然,如果团队完全自研、预算有限,JMeter 完全够用,性能测试的核心思路两边是通用的。
2. VueGen 脚本开发:不只是“录一下就能跑”
2.1 录脚本前先把协议和录制模式选对
很多初学者在 VuGen 里新建脚本时直接点“Record”,结果录完发现脚本全是空请求,或者回放时到处报错。第一个最容易踩的坑就是协议选错。
LoadRunner 录制脚本的原理是监听客户端和服务器之间的通信,把请求“翻译”成脚本函数。对于绝大多数 Web 系统,选择 HTTP/HTML 协议就够了。如果被测对象是移动端 App 后端接口,或者系统里有 WebService,你需要根据实际通信方式选择 WebService、TLS 或者 Socket 等协议。
选择协议后还有一个隐藏选项:Recording Options 里可以选择“HTML-based Script”还是“URL-based Script”。
HTML-based 录出来的是偏用户行为的脚本,比如web_url、web_submit_data,可读性强,适合页面性业务流程。URL-based 录出来的是每个底层请求,脚本会更碎,但适合客户端请求包含大量 Ajax、自定义 Header 或者需要精确控制请求顺序的场景。
我个人的建议是:做业务压测优先用 HTML-based,它生成的脚本更容易做参数化和关联;做接口性能测试直接手写脚本或者用 URL-based,别依赖录制。
2.2 录出第一条“干净”的脚本,然后加事务
录制脚本时要养成一个习惯:把业务步骤控制在一条完整链路上。比如登录、查询订单、提交订单,这是一个有价值的用户操作链路;不要从打开首页到退出登录都录进去,脚本太长不好维护,也容易引入无关噪音。
录完脚本后,VuGen 会把脚本放在 vuser_init、Action、vuser_end 三个区段里。vuser_init 通常存放登录等初始化操作,每个虚拟用户只会执行一次;Action 是迭代执行的段,真正压测时反复运行的就是这部分;vuser_end 放退出、清理操作。
最要紧的是给关键业务加事务。没有事务标记的脚本,LoadRunner 只知道“请求成功”,不知道“登录耗时多少”“提交订单耗时多少”。你需要圈出业务起点和终点,像这样:
Action() { lr_start_transaction("登录"); web_url("登录页", "URL=http://测试服务器/login", "Resource=0", "RecContentType=text/html", LAST); web_submit_data("登录提交", "Action=http://测试服务器/login", "Method=POST", "RecContentType=text/html", "ItemData=Name=username, Value={username}, ENDITEM", "ItemData=Name=password, Value={password}, ENDITEM", LAST); lr_end_transaction("登录", LR_AUTO); lr_start_transaction("查询订单"); // 中间省略查询请求 lr_end_transaction("查询订单", LR_AUTO); return 0; }需要注意的一点:事务名不能重名,嵌套顺序要小心。如果你在登录事务还没结束时又开始了下一个事务,那么前一个事务的耗时统计就会包含后一段逻辑,数据会失真。还有,不要把lr_start_transaction放在 vuser_init 里,因为 init 只跑一次,它统计不了迭代场景下的业务波动。
2.3 参数化:别让一千个虚拟用户共用同一个账号
功能测试里一个账号反反复复登录没问题,性能测试不行。真实用户注册了不同账号,服务端为了鉴权、防重、数据隔离,往往对同一账号的并发操作有限制。如果你不参数化,一百个虚拟用户用同一个账号并发登录,可能压出来的“响应慢”其实是被锁账号导致的。
参数化操作不复杂。在脚本里选中要替换的字符串,右键选择“替换为新参数”,然后在参数属性里配置数据来源。常见做法是准备一个 CSV 文件,里面放用户名和密码数据,参数名分别为 username、password,脚本里用{username}、{password}引用。
真正容易出错的是下面两个设置:“选择下一行”和“更新值的时间”。
| 设置项 | 可选值 | 使用场景 |
|---|---|---|
| Select next row | Sequential / Random / Unique | 决定每个虚拟用户每轮迭代取的是哪一行数据 |
| Update value on | Each occurrence / Each iteration / Once | 决定同一个 Vuser 在多少次请求内更新参数值 |
举个例子:登录语句里用了 username 参数,如果设置成 Sequential + Each iteration,那么第一个 Vuser 第一次迭代取第一行,第二次迭代取第二行;第二个 Vuser 第一次迭代也取第一行。如果希望每个 Vuser 各自锁定一个账号,则要设置成 Unique + Once,再设置迭代次数不超过数据行数。
最稳妥的测试方式是让每个用户的请求路径和数据彼此隔离。你可以为每个 Vuser 准备独立的参数集,比如用户编号段,测试结束后导出日志时也方便回溯“是哪个用户的数据出了问题”。这样后续定位服务端问题会省很多时间。
2.4 关联:处理服务器动态返回的核心技术
录制脚本最大的问题不是“录不下来”,而是“录下来了但回放失败”。常见原因是服务器每次返回的动态值不一样,比如登录返回的 sessionId、下单返回的订单号、页面里隐藏的 token。这些动态值在录制时被写死到了脚本里,回放时服务器发现值不匹配,直接拒绝请求。
解决办法是关联:先从服务器返回值里把动态值抓出来,再在后续请求中替换成这个动态值。
LoadRunner 有自动关联功能,但我不建议你完全依赖它。自动关联经常会抓到一堆没用的动态数据,而且有的动态数据是经过 JS 计算的,自动识别未必准确。
手写关联的核心是找边界。比如服务器返回内容里有"sessionId":"abc123",为了抓到 abc123,可以在请求前用web_reg_save_param:
web_reg_save_param("sessionId", "LB=sessionId\":\"", "RB=\"", "Search=Body", "NOTFOUND=warning", LAST); web_url("获取会话", "URL=http://测试服务器/api/session", "Resource=0", LAST); // 后续请求携带该动态值 web_add_header("X-Session-Id", "{sessionId}"); web_url("查询订单", "URL=http://测试服务器/order/list", "Resource=0", LAST);关联函数有个特点:它必须在真正的请求之前注册,因为 LoadRunner 是先用这个函数去“预登记”一个捕获逻辑,再在后续请求返回时提取数据。很多新手把它放在请求后面,结果永远抓不到。
另外,边界字符串里的双引号是转义字符的重灾区。你可以先把服务器返回的内容粘贴出来数清楚,左右边界要尽量短但又不能歧义。边界越长越精确,但服务器一改格式,你的脚本就废了;边界太短则容易抓到错误数据。
2.5 思考时间:压测数字像不像真实用户,它很关键
真实用户不会像机器一样一秒不差地连续点击。页面加载完,人总要停顿一下、看一眼、输入点什么。这个间隔就是思考时间。录制时 LoadRunner 会自动生成lr_think_time,默认回放时会按录制的延迟执行。
场景里要不要保留思考时间,取决于你的压测目的。做容量估算时,保留合理的思考时间能模拟真实业务压力;做接口最大吞吐测试时,通常要去掉思考时间,看系统在不喘气的情况下的极限。
我见过不少团队为了追求“压得更狠”把所有 think time 都删掉,结果压出来的吞吐量虚高,上线后又达不到这个数据。适当保留思考时间反而更接近生产观测。你可以给lr_think_time设置一个随机范围,比如 2 到 5 秒,模拟真实用户的行为波动。随机范围比固定值更接近实际情况,也能减少多个虚拟用户同时“同步点击”造成的压力尖峰。
3. Controller 场景设计:从脚本到压力的关键一跳
3.1 场景类型怎么选:普通并发还是目标导向
脚本做好以后,进入 Controller 新建场景。LoadRunner 2022 R1 里常见的有两种场景玩法:手动场景和基于目标的场景。
手动场景适合调查型测试。你明确地设置并发用户数、加载时间和运行时长,比如“100 个用户,1 分钟加载完,持续 15 分钟”。这种玩法能给你一个确定性的负载形态,方便对比不同版本的表现。
基于目标的场景适合验收型测试。你在场景里设置一个目标值,比如“每秒事务数要达到 50”,LoadRunner 会逐步增加虚拟用户数,直到达标或者系统到达瓶颈。如果你的需求就是“支撑每秒 100 笔订单”,这种场景能给团队一个直观结论。
从实践来看,不要一上来就直奔高强度压力。我会先跑一遍单用户脚本,确认脚本本身没有报错、思考时间设置合理;再用小并发比如 5 个用户跑一遍,观察基础数据;最后才按梯度往上加。
3.2 用户加载方式:一次性铺开是新手最常见的暴力操作
场景设计中,加载策略比并发数本身更容易被忽略。很多测试计划写成“启动 200 个用户”,执行时直接 200 个并发同时冲进来。真实业务不是这样的,用户是在一个时间段内陆续到达的。
LoadRunner 里可以把加载过程分成多段,比如前 5 分钟每秒增加 5 个用户,保持 20 分钟,再让压力自然下降。这样做有两个好处:一是给系统一个预热过程,JIT 编译、缓存填充、连接池初始化都需要时间;二是你能在渐增的过程中观察响应时间从哪个用户数开始劣化,相比之下比较容易定位拐点。
持续时间也不是越长越好。压测时长取决于业务场景:做稳定性测试,我会让系统在 70% 到 80% 目标负载下持续跑几个小时甚至一夜;做短时压力测试,30 分钟以内也能看出问题。关键是场景停止时要设计“软着陆”,不要让 200 个用户在同一秒全部断开,否则服务端可能把断连误判成故障,日志里全是连接重置。
3.3 资源监控:别等系统挂了才发现问题
只盯着事务响应时间看是远远不够的。响应时间上升可能是应用服务器 CPU 打满,也可能是数据库慢查询,还可能是带宽挤满。你需要在压测进行时同步监控服务器资源。
Windows 环境监控常见计数器:Processor 的 % Processor Time、Memory 的 Available MBytes、PhysicalDisk 的 % Disk Time。Linux 环境 LoadRunner 支持 Unix Resources 监控,但依赖 rstatd 服务。很多 Linux 发行版默认没安装 rstatd,连接不上时不要慌,先在目标机器上确认 rpcbind 和 rstatd 是否正常。
容器化和云主机环境下,监控手段会更多样。比如直接在命令侧用top、vmstat、dstat记录现场,甚至从云平台拉监控曲线。无论用哪种方式,压测现场留下的监控数据必须和 LoadRunner 的分析结果能对上时间轴,否则分析阶段就没有依据了。
4. Analysis 结果分析:从一堆图表里找线索
4.1 拿到报告先看哪几个指标
压测结束,Analysis 会自动打开结果会话。新手容易一下子扎进几十个图表里,其实第一步应该看摘要。
摘要页里要优先确认三件事:第一,事务总数和成功数是否符合预期;第二,平均响应时间是否达标;第三,错误率有没有异常。如果错误率不是 0,先去看错误日志,搞清楚是脚本参数问题还是服务端返回 4xx/5xx。错误没有定位之前,后面的平均值都有误导性。
还要注意“平均响应时间”这个指标的局限。如果 99% 的请求都是 10 毫秒,1% 的请求是 10 秒,平均值会把问题掩盖掉。所以实际分析中要多看响应时间的分布、90 分位、95 分位,而不是只有一个平均值就下结论。
4.2 四张图表组合起来读
我比较常用的组合是平均事务响应时间、每秒事务数(TPS)、吞吐量和 HTTP 响应分类。
平均事务响应时间曲线看业务体验变化;TPS 曲线看系统处理能力是否随用户增长而增长;吞吐量曲线看数据流量趋势,可以对照带宽是否成为瓶颈;HTTP 响应分类能直观看到 200、404、500、超时请求的数量。
读图时不要只看最后一段,要看整体的变化过程。正常系统在压力渐增时,TPS 会跟着涨,响应时间会缓慢增加;当系统饱和后,TPS 不再增加甚至下降,响应时间快速上升,此时对应的用户数就是系统的实际容量拐点。再结合 CPU、内存、磁盘等资源曲线,基本能判断瓶颈出在哪一层。
4.3 典型曲线形态:什么情况算“异常”
一种形态是 TPS 一直平、响应时间也不涨,到了某个用户数突然大面积报错。这种情况常见于网关限流、连接数限制或者数据库连接池打满。
另一种形态是用户数还在增加,但 TPS 不再涨,响应时间快速恶化。这种情况多半是服务端已经到了处理上限,比如单实例 CPU 打满,加用户也不再提高吞吐量,反而因为排队拖慢已有请求。
还有一种形态是压测期间整体表现不错,但运行一段时间后 TPS 逐步下降。这往往暗示内存泄漏、临时文件堆积、连接池没有释放等问题。遇到这种形态,一定要把压测时长拉长,观察几个完整周期,不能只跑 5 分钟就收工。
5. LoadRunner 2022 R1 实操中容易踩的坑
5.1 录出来是空脚本,回放也没有请求
这个问题我在帮同事排查时遇到过好几次。原因通常是协议选择不正确,或浏览器走了系统代理以外的通道。LoadRunner 录制时会在本机启动一个代理端口,浏览器必须走到这个代理上才会被抓到请求。
如果是 HTTPS 站点,还有证书问题。录制时会弹证书信任提示,没有正确信任的话请求也可能抓不全。我的排查顺序是:先看录制时浏览器能否正常打开页面,再检查录制选项里的代理和端口,最后看协议列表。
更保险的做法是:不依赖自动录制,直接用捕获的接口文档手写脚本。很多业务系统尤其是接口型项目,录制反而是绕了一大圈。
5.2 参数化“更新策略”选错,导致隐性失败
脚本回放时登录成功,压测跑起来却大量报认证失败。排查脚本时发现参数值都取到了,但用户数据已经被前面的迭代用掉。
比如你要模拟 100 个用户各登录一次,参数设置成“Sequential + Each occurrence”,前面 50 个 Vuser 可能把 50 行数据用掉了,后面的 Vuser 取到重复数据,被服务端踢掉。改成“Unique + Once”后,问题立刻消失。
参数化数据量也要留有余量。如果并发用户是 100,数据行却只准备了 60 行,那么剩余用户的取值就会出现“用尽”或“循环”,进而导致压出的结果含杂质。准备数据时要考虑整个测试时长内所有迭代的总消耗量,不要只看单次脚本。
5.3 Linux 资源监控连不上
这个坑很典型。Controller 里添加 Unix Resources 监控,目标机开启 rstatd 后依然连不上。很多云主机的安全组默认屏蔽了相关 RPC 端口,或者在系统层面没有安装完整的 rpcbind 套件。
我在实践中的做法是:不把资源监控全部押在 LoadRunner 的一个功能上。压测的同时用dstat在目标机上直接记录日志,或者让运维从监控平台导出 CPU、内存、磁盘、网络曲线。LoadRunner 的监控图表适合做统一报告,但现场数据备份更重要,二者对得上时间轴就行。
5.4 压测跑到一半脚本全部失败,先分清是脚本还是系统
压力一高,大量报错出现时,第一反应往往是“系统崩了”。但别忘了先看错误信息的归属。
如果错误是“连接超时”“SSL 握手失败”,可能是服务端过载或负载生成机与服务端之间的网络带宽被占满。如果错误是“响应内容未找到”“关联动态值未捕获”,很可能是脚本中的动态处理不够健壮,压力大了之后页面中的 token 或 session 获取失败。
遇到大批量失败,我会在场景里临时打开更详细的日志,截取一条失败请求的完整服务端响应再判断。千万不要在没定位错误码的时候贸然重跑场景,那样只是重复烧机器时间。
5.5 容易被忽略的场景级配置
有几个配置项对新手很有迷惑性。比如集合点,脚本里写了lr_rendezvous,但如果在 Controller 里没有启用集合点设置,它不会真正同步等待。集合点的作用是把大量虚拟用户集中在同一个操作上发起压力,适合模拟秒杀、整点开抢这类场景。默认不启用时,每个用户跑到那里就自己往下走了。
还有迭代次数与脚本结构的关系。如果我们希望压测模拟的是用户反复下单,那 Action 部分要能够安全重复执行;如果脚本里有很多随业务变化的动态数据,重复迭代时要注意会不会因为上一次执行留下了状态,导致后续请求失败。建议 Action 保持“无状态”设计,每次迭代都能独立跑通。
6. 完整工作流与个人习惯建议
6.1 从需求到报告:一条可复用的流程
我推荐新手把性能测试拆成固定几个阶段,前一个阶段的产出是后一个阶段的输入。
第一步是明确指标,比如目标 TPS、响应时间上限、错误率上限、并发用户数范围。第二步是录制和开发脚本,至少要包含一个主链路和一个次链路。第三步是参数化与关联,确保脚本回放稳定。第四步是在 Controller 里设计小并发冒烟场景,跑通后逐梯度加压。第五步是执行正式场景,同时记录目标机资源。第六步是导出 Analysis 报告并整理成结论:瓶颈在哪、系统是否达到目标、建议是什么。
这套流程看起来简单,但每一条都做到位不容易。很多人跳过第二步直接上来压,最后只会得到一堆看似华丽但无法解释的数字。
6.2 设计用例前,先把这七个问题问清楚
做性能测试最怕的是需求没对齐就开跑。接到任务时,我会先和业务方确认:预期的峰值用户量是多少?这个峰值是每天出现一次还是持续一段时间?核心业务路径是哪几条?响应时间达标线是多少,是平均值还是 95 分位?测试环境的数据量和生产环境差多少?客户端网络条件是内网还是互联网?允许的最大错误率是多少?
这些问题直接影响场景设计和数据分析。比如目标环境网络延迟很高,你测出来响应时间偏长,不代表业务系统慢,可能是客户端链路本身的问题。如果数据量只有生产的百分之一,数据库查询表现会远好于生产,直接外推很容易误判。
6.3 我的一点实践体会
最后分享一个工作习惯:每次压测跑完,我会把场景文件、脚本压缩包、Analysis 原始结果和现场监控日志一起归档。下一次优化后要复现同样规模的测试,直接打开原始结果对比就能看出差异在哪,不需要靠“我记得上次状态挺好”这种模糊记忆。
LoadRunner 2022 R1 这套工具并不复杂,复杂的是你对业务的理解、对数据的判断和对瓶颈的定位能力。先把基础工作流跑顺,再慢慢积累自己的经验库,性能测试这条路就能越走越稳。