
1. 项目概述为什么选择LoadRunner测试Java Web应用在性能测试这个行当里干了十几年我见过太多团队在项目上线前手忙脚乱的样子。服务器一上压力就崩响应时间从几百毫秒飙升到几十秒用户投诉像雪花一样飞来——这些问题十有八九都能在性能测试阶段提前发现并解决。今天我想聊的就是如何用性能测试领域的“老炮儿”工具LoadRunner来给咱们的Java Web应用做一次全面的“体检”。LoadRunner这个名字对于很多刚入行的测试工程师来说可能既熟悉又陌生。熟悉是因为它名声在外是性能测试工具的标杆陌生则是因为它的学习曲线相对陡峭尤其是脚本编写部分让不少人望而却步。但我想说的是一旦你掌握了它的核心逻辑和技巧它带来的回报是巨大的。它能模拟成千上万的虚拟用户对服务器发起真实、可控的压力精准地定位出系统的瓶颈到底是在数据库连接池、JVM内存还是某个慢SQL上。为什么是Java Web应用因为这是目前企业级应用最主流的架构之一。从传统的Spring MVC到现在的Spring Boot微服务Java生态的健壮性和复杂性并存。一个看似简单的登录接口背后可能牵扯到Session管理、缓存穿透、数据库锁等一系列问题。用LoadRunner测试它不仅仅是看接口能不能返回“200 OK”更是要深入检验在高并发下线程池会不会被打满、GC垃圾回收会不会频繁导致“Stop-The-World”、数据库连接会不会泄漏。这些深层次的问题普通的功能测试或者用Postman手动点几下是根本发现不了的。所以这篇文章的目标很明确我不打算给你讲LoadRunner有多少个菜单、每个按钮是干嘛的——这些官方文档和入门教程里都有。我要做的是结合我这些年踩过的坑、总结的经验手把手地带你走一遍从零开始用LoadRunner对一个典型Java Web应用比如一个电商系统的下单接口进行性能测试的全过程。重点会放在最核心也最让人头疼的脚本编写上我会分享一些教科书里不会写的、能让你事半功倍的技巧。无论你是刚开始接触性能测试的新手还是想深化LoadRunner脚本编写能力的老手相信都能从中找到对你有用的东西。2. 测试环境搭建与核心概念扫盲在动手写脚本之前把“战场”准备好是关键。很多人一上来就急着录制脚本结果因为环境没配好录出来的脚本根本回放不了或者回放的结果毫无意义白白浪费大量时间。2.1 LoadRunner三大组件与Java Web测试的适配LoadRunner主要包含三大组件Virtual User Generator (VuGen)、Controller、Analysis。对于Java Web应用测试我们的主战场是VuGen。VuGen (虚拟用户生成器)这是脚本编写和调试的地方。它通过录制你在浏览器或其他客户端上的操作生成基础的脚本。但请注意对于Java Web应用特别是前后端分离、大量使用Ajax和RESTful API的现代应用单纯的HTTP/HTML录制往往不够我们需要深入理解其通信协议。最常用的是“Web - HTTP/HTML”协议。它足够通用能捕获到绝大多数HTTP请求和响应。如果你的应用使用了WebSocket或自定义的二进制协议那可能需要选择“WebSocket”或“Windows Sockets”协议但后者需要手动构造报文复杂度陡增。我们今天的讨论基于最普遍的HTTP/HTML协议。Controller (控制中心)在这里设计并运行你的测试场景。你可以设置有多少虚拟用户Vusers同时运行、他们以什么节奏Ramp Up/Ramp Down启动和停止、压力来自哪些负载生成器Load Generators。测试Java Web应用时一个关键设置是思考时间Think Time和步Pacing。完全去掉思考时间进行“狂轰滥炸”式的测试适合寻找系统的绝对瓶颈如最大TPS但不符合真实用户行为。更真实的场景是设置合理的思考时间比如用户浏览商品详情页的几秒钟和请求间隔这能帮你发现系统在持续、平稳压力下的表现。Analysis (分析器)测试结束后所有数据都在这里。它能生成各种图表告诉你平均响应时间、每秒事务数TPS、错误率、服务器资源需配合监控工具等。分析的关键不是看单个数字而是看趋势和关联。例如当并发用户数上升时响应时间是否呈指数级增长TPS在达到一个峰值后是否不再上升甚至下降同时服务器的CPU、内存、磁盘I/O、网络带宽是否出现瓶颈数据库的活跃连接数、慢查询是否激增将这些数据关联起来才能准确定位瓶颈。注意LoadRunner的安装过程本身不算复杂但有几个坑需要注意。首先确保你的操作系统Windows有足够的权限最好关闭杀毒软件实时防护避免安装或录制时被拦截。其次安装路径不要有中文或特殊字符这是很多国外软件的通用禁忌。最后如果你需要测试HTTPS的应用在安装过程中或首次录制前务必按照指引安装LoadRunner的根证书到“受信任的根证书颁发机构”否则无法解密和录制HTTPS流量。2.2 Java Web应用侧的准备工作测试不是你一个人的战斗需要开发、运维同事的配合。准备测试环境最好有一个独立的、与生产环境架构一致的测试环境Staging Environment。在这个环境上部署你要测试的Java应用。确保数据库里有足够但合理的数据量比如百万级用户、千万级订单因为空库和满库的性能表现天差地别。可以使用工具或编写脚本进行数据构造Data Fabrication。开启应用监控这是定位性能瓶颈的“眼睛”。你需要监控JVM使用jvisualvm、JConsole或更专业的APM工具如SkyWalking, Pinpoint监控堆内存使用情况、GC频率和耗时、线程池状态。应用服务器如Tomcat监控活跃线程数、连接数、请求处理时间。数据库监控慢查询日志、活跃连接数、锁等待情况。系统资源使用top(Linux)、Performance Monitor(Windows) 或nmon监控CPU、内存、磁盘I/O、网络流量。识别关键业务场景与接口和产品经理、开发工程师一起确定本次性能测试要覆盖的核心业务场景。例如对于一个电商系统优先级最高的可能是“用户登录”、“商品搜索”、“下单支付”。明确这些场景对应的后端API接口URL、请求方法、参数格式。3. 脚本录制、增强与参数化实战录制脚本只是万里长征第一步录下来的原始脚本非常“脆弱”且“死板”直接用于压力测试几乎肯定会失败。脚本增强才是体现工程师价值的地方。3.1 首次录制与协议选择打开VuGen创建一个新脚本选择“Web - HTTP/HTML”协议。在录制选项中选择浏览器推荐Chrome或IE兼容模式填入你的Java Web应用首页地址如http://test-app:8080。开始录制后VuGen会启动浏览器并开始代理所有HTTP/HTTPS流量。这时你就像正常用户一样在浏览器上完成你的测试场景操作比如打开首页 - 登录 - 搜索商品 - 查看商品详情 - 加入购物车 - 下单。操作完成后停止录制。VuGen会自动生成一个脚本主要包含web_url,web_submit_data,web_custom_request等函数。你会立刻发现几个问题所有请求中的服务器地址、会话ID如JSESSIONID都是录制时的硬编码。搜索的关键词、商品ID等都是固定的。脚本里可能包含了很多不必要的请求比如图片、CSS、JS等静态资源这些通常应由CDN或Nginx处理不应计入核心业务压力。3.2 脚本清理与关联Correlation—— 第一个核心技巧关联是LoadRunner脚本编写的灵魂目的是将服务器返回的动态值如Session ID、Token、订单号保存为变量并在后续请求中自动使用。为什么需要关联因为服务器每次会话都会生成唯一的JSESSIONID如果你在脚本里写死了录制时的那个ID那么第二个虚拟用户运行时用的还是第一个用户的Session会导致会话混乱、登录状态失效测试完全失去意义。如何做关联VuGen有自动关联功能但不太智能。我强烈建议手动关联这能让你更理解应用。找到动态值回放一遍脚本在回放日志Replay Log中查找失败请求。通常失败是因为服务器返回了“无效的会话”。查看该请求之前的那个服务器响应在Tree View视图里看最直观寻找类似JSESSIONIDASDFGHJKL123456这样的字符串。设置关联规则在脚本中找到需要这个动态值的请求通常是下一个请求。使用web_reg_save_param函数。这个函数是一个“注册型”函数必须放在它要捕获数据的HTTP请求之前。// 在请求前注册捕获响应中的JSESSIONID web_reg_save_param(sessId, LBJSESSIONID, RB;, SearchBody, LAST); // 接下来是发送请求的函数如 web_url web_url(homepage, URLhttp://test-app:8080/home, ...);LB和RB是左边界和右边界用于精准定位要捕获的文本。SearchBody表示在响应正文中搜索。使用关联变量捕获到的值会保存在sessId变量中实际是{sessId}。在后续需要携带该Session的请求中比如提交表单你就需要修改请求web_submit_data(login, Actionhttp://test-app:8080/login, MethodPOST, EncTypeapplication/x-www-form-urlencoded, RecContentTypetext/html, ITEMDATA, Nameusername, Valuetestuser, ENDITEM, Namepassword, Value123456, ENDITEM, Namejsessionid, Value{sessId}, ENDITEM, // 使用关联变量 LAST);实操心得关联的边界LB/RB选择要足够唯一。如果左右边界太普通可能会捕获到错误的内容。一个技巧是在Tree View里选中响应中的动态值右键选择“Create Parameter”VuGen会自动生成一个web_reg_save_param_ex更强大的版本函数框架你只需要微调边界即可。对于现代应用使用的JSON Web Token (JWT)关联方法类似只是边界变成了LB\token\:\和RB\。3.3 参数化Parameterization—— 让测试数据“活”起来你肯定不希望10万个虚拟用户都用同一个账号“testuser”登录或者都搜索同一个关键词“手机”。参数化就是为了解决这个问题。参数化步骤创建参数文件在VuGen中选中脚本中需要参数化的静态值如用户名右键选择“Replace with a Parameter”。给它起个名字比如“username”。设置参数属性打开参数列表选择参数类型。最常用的是“File”类型。文件内容创建一个文本文件如username.dat每行一个值如 user001, user002,...。也可以使用两列用逗号分隔实现用户名和密码的配对。选择模式Sequential顺序每个Vuser按顺序取数据。适合需要保证数据唯一性的场景如注册。Random随机每次随机取。更接近真实情况但可能重复。Unique唯一每个Vuser取唯一的值用完后可以停止或循环。这是最常用的模式能确保并发时数据不冲突。更新时机通常选择“Each iteration”每次迭代更新。在脚本中使用参数参数化后原来的Valuetestuser就变成了Value{username}。高级技巧使用数据池Data Pool管理海量数据当需要模拟大规模用户如10万时手动创建10万个用户名文件不现实。可以编写一个简单的Python或Shell脚本利用规则如前缀数字批量生成数据文件。在参数设置中将这个大数据文件作为源并设置为“Unique”模式LoadRunner会自动为每个Vuser分配唯一的数据块。3.4 事务Transaction与检查点Checkpoint事务用来衡量一个业务操作的性能。比如我们把“登录”这个操作定义为一个事务。lr_start_transaction(用户登录); // 事务开始 web_submit_data(login, ...); lr_end_transaction(用户登录, LR_AUTO); // 事务结束LR_AUTO自动判断成功失败在Analysis报告中你会看到“用户登录”这个事务的平均响应时间、通过率、TPS等关键指标。检查点用来验证请求是否真正成功。HTTP状态码是200并不代表业务成功比如登录失败也可能返回200但页面提示“密码错误”。我们需要检查响应内容中是否包含成功的关键字。web_reg_find(SearchBody, Text登录成功, SaveCountlogin_ok_count, // 将匹配次数保存到变量 LAST); web_submit_data(login, ...); // 在事务结束后可以根据 login_ok_count 的值手动判断事务成功与否 if (atoi(lr_eval_string({login_ok_count})) 0) { lr_end_transaction(用户登录, LR_PASS); } else { lr_end_transaction(用户登录, LR_FAIL); }这能确保我们统计的“成功事务”是业务意义上的成功而不仅仅是网络层面的成功。4. 场景设计、执行与监控脚本调试通过后就可以在Controller中设计真实的压力场景了。4.1 设计符合业务模型的场景不要一上来就设置1000个用户并发运行1小时。科学的性能测试是分阶段的基准测试Baseline Test用1个或少量用户运行脚本。目的是验证脚本正确性并获取在无压力情况下各个事务的响应时间基准。这个数据将作为后续对比的参照物。负载测试Load Test逐步增加并发用户数如50100200...观察系统性能指标的变化。目标是找到系统在预期负载下的性能表现并确定性能拐点。这里的关键是“逐步”使用“Ramp Up”功能让用户在10-15分钟内慢慢启动避免对系统造成启动冲击。压力测试Stress Test在负载测试找到的拐点附近或略高于预期峰值的位置持续施加压力一段时间如30分钟。目的是看系统在极限压力下是否稳定是否有内存泄漏、连接不释放等问题。稳定性测试Endurance Test / Soak Test用中等压力如预期峰值的70-80%长时间运行如8小时、24小时甚至更久。目的是发现系统在长期运行后是否存在性能衰减如内存缓慢增长、数据库连接池逐渐耗尽等。在Controller中你可以通过“Schedule by Scenario”或“Schedule by Group”来精细控制每组用户的行为模式。4.2 集成监控与实时分析在Controller运行场景时一定要打开集成的资源监控器或通过其他监控工具。添加对测试服务器的监控指标如Windows Performance Counter或Linux的rstatd守护进程提供的指标。你需要实时关注用户侧运行中的Vusers数量、每秒事务数TPS、事务响应时间、错误数。服务器侧CPU使用率重点关注%Processor Time和每个核心的使用率、内存使用量特别是Java堆内存的Used和Committed、磁盘队列长度和读写速度、网络吞吐量。应用侧通过APMJVM GC频率Young GC/Full GC、线程池活跃线程数、关键方法的执行时间、数据库慢查询。一个经典的性能问题定位流程是当TPS上不去而响应时间飙升时立刻查看服务器监控。如果CPU接近100%可能是代码有计算密集型瓶颈或死循环如果内存使用率持续增长且Full GC频繁可能存在内存泄漏如果磁盘I/O等待时间很长可能是数据库查询慢或日志写入过于频繁。5. 结果分析与性能瓶颈定位实战测试跑完重头戏才刚刚开始。Analysis报告里数据繁多如何从中读出故事5.1 核心图表解读运行Vuser图确认虚拟用户是否按计划启动和停止。每秒事务数图这是黄金指标。健康的曲线应该是随着用户数增加TPS平稳上升在达到系统最大处理能力后形成一个平台。如果TPS在达到峰值后急剧下降说明系统可能发生了崩溃或严重阻塞。事务平均响应时间图响应时间应随压力增加而缓慢上升。如果出现陡增的“拐点”这个拐点对应的并发用户数就是系统的性能瓶颈点。将这张图与TPS图对照看拐点往往对应着TPS的峰值平台起点。点击率图反映服务器每秒收到的HTTP请求数。它可以和TPS对比。如果点击率很高但TPS很低说明很多请求是无效的比如返回了错误页面或者静态资源请求占比过大。系统资源监控图将CPU、内存、磁盘、网络图与事务性能图在时间轴上对齐。当性能恶化时看哪种资源首先到达瓶颈如CPU持续95%内存占用率90%。5.2 从现象到根因的排查思路案例一TPS低响应时间长但服务器CPU和内存都很空闲。可能原因瓶颈不在应用服务器本身。排查方向网络检查网络延迟和带宽。在服务器端用ping、traceroute或mtr检查到数据库或下游服务的网络状况。数据库这是最常见的瓶颈点。检查数据库服务器的CPU、IO。在数据库端监控慢查询日志。很可能是一条没有索引的SQL在并发下拖垮了整个系统。使用EXPLAIN分析可疑SQL的执行计划。外部依赖你的Java应用是否调用了外部API、消息队列、缓存服务这些外部服务的性能可能成为瓶颈。检查它们的响应时间和可用性。案例二压力测试初期正常运行一段时间后TPS逐渐下降响应时间上升服务器内存使用率持续缓慢增长。可能原因典型的内存泄漏。排查方向JVM堆内存分析使用jmap -histo:live pid查看存活对象 histogram或者使用jmap -dump:live,formatb,fileheap.bin pid导出堆转储文件然后用MATMemory Analyzer Tool或JVisualVM分析。重点查看是否有某个类的对象数量异常多且持续增长如Session对象、缓存对象未及时清理。检查代码重点审查静态集合类如static Map、缓存实现、数据库连接池、文件流等资源是否未正确关闭。案例三高并发下事务失败率突然增高日志中出现大量数据库连接超时或获取连接失败的异常。可能原因数据库连接池配置不当。排查方向检查连接池配置如HikariCP或Druid的maximumPoolSize最大连接数是否设置过小connectionTimeout获取连接超时时间是否太短。检查数据库最大连接数应用连接池的最大连接数不能超过数据库服务器设置的全局最大连接数。检查连接泄漏是否有地方获取了数据库连接或其它资源后在异常情况下没有在finally块中释放这会导致连接池中的连接被慢慢耗尽。5.3 编写有价值的性能测试报告报告不是数据的堆砌而是问题的分析和解决方案的建议。一份好的报告应包含测试概述目标、范围、环境、工具。测试场景与脚本简要说明模拟了哪些业务并发用户设计。性能指标摘要以表格形式列出核心事务在基准、负载、压力测试下的TPS、平均响应时间、90%响应时间、通过率等并与需求或历史基线进行对比。关键图表分析附上TPS、响应时间、资源使用率的趋势图并配上文字说明指出性能拐点、瓶颈点。瓶颈定位与根因分析这是报告的核心。详细描述发现的问题如“在200并发时下单事务响应时间从500ms陡增至5s”展示你关联分析的证据如“此时数据库服务器CPU达到100%并发现一条涉及全表扫描的慢查询”。优化建议针对每个瓶颈点给出具体、可实施的建议。例如“建议为product表的category_id字段添加索引预计可降低该查询耗时90%。”“建议将JVM堆内存从2G调整到4G并优化Young区比例以减少Full GC频率。”风险与结论总结当前系统的性能水平是否满足上线要求。如果不满足主要的性能风险是什么。性能测试的价值最终就体现在这份报告和随之而来的系统优化上。它让技术决策从“我觉得”变成了“数据证明”。