ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

JMeter全链路性能测试:百万级造数、监控调优与报告落地

JMeter全链路性能测试:百万级造数、监控调优与报告落地

简介:这份《性能测试-Jmeter》培训课件面向测试工程师、开发人员及需要承担性能测试任务的技术团队,围绕JMeter实战梳理从数据准备到报告复盘的完整链路。内容覆盖数据制造、性能测试方案设计、脚本执行、性能监控与调优、测试报告编写五个环节,并结合云影像等业务场景说明初始化数据、铺底数据与参数化数据的准备思路,以及需求规格、环境清单、同步定时器、聚合报告、PerfMon监控和持续集成执行等要点。资源包共1个pptx文件,大小约12.48MB,以图文课件形式组织,目录按五个模块展开,便于直接用于内部培训或自学翻阅,可帮助读者建立可落地的性能测试流程框架,并理解常见瓶颈定位与调优方向。目前已有1150人学习,适合希望系统入门或查漏补缺的初中级性能测试从业者。

1. 性能测试项目从造数据到调优:一份 JMeter 全链路培训方案的落地拆解

性能测试最容易出问题的环节是准备阶段:脚本写好了,环境也备齐了,真正跑起来才发现数据库里只有几百条数据,查询快得没有参照价值;或者并发刚开始增加,服务端就已经资源异常,而你连瓶颈在数据库还是在应用层都判断不了。这份 JMeter 培训方案解决的正是这类问题:它把性能测试拆成造数据、方案设计、脚本执行、监控调优、报告输出五段,每一段都给出可执行的操作和判断依据。方案面向两类读者:需要交课程设计或毕业设计的学生,以及要把性能测试真正做进项目交付流程的测试工程师。JMeter 5.1.1 是全流程的主力工具,配套 Oracle 19c、Tomcat、jconsole 和 Jenkins 一起使用。

2. 造数据:两种路径的取舍与百万级数据落地

造数据是性能测试的第一步,也最容易被跳过。很多方案在需求里写着“支持 100 万条影像登记数据”,测试时库里只有几万条,索引命中率、分页查询、统计报表的表现完全不同,压出来的结论自然不可信。方案里把造数据分成两类路径,并且明确给出了各自的适用边界。

2.1 先分清三类数据:初始化、铺底与参数化

方案把测试数据分成三类,这个划分决定了后面的工作量分配。初始化数据是系统运行必需的基础配置,比如字典表、科室表、权限表,量不大但不能缺;铺底数据是历史累积数据,也就是让表“有厚度”的那部分,云影像场景里就是 100 万条登记记录、100 万条患者就诊记录,涉及 EXAM_REQUEST、EXAM_PATIENTVISIT 这类主表;参数化数据是脚本每次请求要变化的字段,比如患者姓名、证件号、登记时间。

三类数据的制造方式不一样。初始化数据一般直接由开发提供 SQL 脚本;铺底数据是工作量的大头,决定了用哪种造数方式;参数化数据则依赖 JMeter 的 CSV Data Set Config 或者函数助手。我一般会先让项目组确认每张目标表需要的量级,然后把它拉成一张清单,避免做到一半发现漏了关联表。

2.2 接口造数与存储过程造数的对比

方案里给了两种造数路径,对比维度很实在:

对比项JMeter 接口造数存储过程或 Java 代码造数
实现方式抓包拿到保存接口,参数化后模拟 HTTP 请求直接按表关系往数据库插数据
速度约 2–5 万条/小时约 10–20 万条/小时
数据真实性高,走完整业务逻辑取决于 SQL 是否写对
主要风险执行慢,占用时间长表关联搞错会产出脏数据
前置条件需要一个可用的保存接口需要完全掌握表关系

选择依据不是哪个更快,而是你对系统熟不熟。方案里的实际选择是接口造数,理由写得很坦白:不熟悉云影像系统的表间关系,而且时间相对充裕。这个判断我认为是对的:百万级数据用存储过程一两个小时就能铺完,但如果 SQL 写错了关联字段,脏数据排查的时间远超省下来的那几个小时,而且脏数据会污染后续所有压测结论,相当于没有补救机会。

存储过程那条路也不是不能用,方案里给了一段 Oracle 样例,思路可以照着改:

-- 循环插入 300 条审计流水记录,核心是 sys_guid() 生成主键、sysdate 生成时间 create or replace procedure T_ESB2_AUDIT_sp as begin for i in 1 .. 300 loop insert into t_esb2_audit_new( FID, VERSION, BIZID, BIZDESC1, BIZDESC3, REQID, RESID, SOURCESYS, TARGETSYS, ESBSVCCODE, BACKSVCCODE, EXGPTN, RESULTCODE, BODY, CREATETIME ) values( sys_guid(), -- 主键,32 位唯一串 '1.0', -- 版本号,按业务实际填 '4aa066b7-...', -- 业务主键,需要与关联表对应 'http://.../PUSH_FORM_TRAJECTORY2D', '192.168.20.14', '63528dad-...', -- 请求 ID '3f7be047-...', -- 响应 ID 'FOSS', 'UIP', 'ESB_FOSS2ESB_PUSH_FORM_TRAJECTORY2D', 'DOP_ESB2DOP_PUSH_FORM_TRAJECTORY2D', 1, 0, '{"code":"12345678","company":"KUAIDI100","msgId":"...","watchStatus":"abort"}', sysdate ); end loop; commit; -- 循环结束统一提交,减少事务开销 end;

这段样例的关键点不在语法,而在两个地方:一是BIZID这类业务字段必须和主表真实对应,否则后续按业务 ID 关联查询时数据对不上;二是commit放在循环外面,每 300 条提交一次,量级再大就要改成按 5000 或 10000 条分批提交,否则 undo 表空间会被撑满。改循环次数、改批次大小、改时间字段,是这段脚本最常调的三处。

2.3 接口造数的五步流水线

选定接口造数后,执行流程是固定的五步,方案里写得可以直接照抄:

第一步,用抓包工具抓出登记保存接口的完整参数,包括请求头里的 token、Content-Type,以及请求体里每个字段。第二步,对会变化的字段做参数化,姓名、证件号、时间戳这类用 CSV 或随机函数生成,固定字段保留原值。第三步,先设 10 个线程跑一次做调试,确认响应全部成功、库里数据条数对得上。第四步,调试通过后再放开批量。第五步,100 万条的量级用 20 个线程循环 50000 次,本地机器跑或者放到 Jenkins 上持续跑。

第五步的两个细节值得单独说。20 线程 × 50000 次 = 100 万,这个组合不是随便定的:线程数太少跑不完,太多会把被测系统压出非预期错误,反而制造失败数据。方案里选 20 线程,是在“跑得完”和“不压垮系统”之间取的折中。至于放到 Jenkins 上,原因是本地 PC 连续跑十几个小时不能关机,中途休眠、断网、系统更新都会打断脚本,跑了一半的数据还得重新核对。

这里有个容易踩的点:造数脚本和压测脚本必须分开。造数脚本的线程组配置、目标接口、数据源都不一样,混在一个 jmx 文件里,后面调整压测并发数时很容易把造数逻辑一起改掉。

提示:造数前先确认目标表的当前数据量,跑完后再 count 一次,两个数字对得上才算造数成功。仅看 JMeter 聚合报告的请求成功率,不能证明数据真的落库了。

3. 性能测试方案设计:需求规格、环境清单与场景编排

脚本能跑只是手段,方案要回答的是“测什么、在什么环境下测、怎么判定通过”。这一章把方案文档里必须固定的三块内容拆开:需求规格、环境清单、场景设计。

3.1 需求规格说明书先明确指标

性能需求一般由项目组提供,形态是一张指标表。典型内容包括:核心交易的平均响应时间上限、95 线响应时间、目标并发用户数、事务成功率要求、系统需要支撑的数据量级。方案里的做法是把这些指标逐条落成可测量的口径,比如“登记保存接口在 200 并发下平均响应时间不超过 2 秒,成功率 100%”。

指标最容易出现的偏差是定义不一致。响应时间是只算服务端处理,还是包含网络传输和前端渲染?并发用户数是线程数,还是活跃用户数?这些不写清楚,测完给出的数字和项目组的预期对不上,报告就白写了。我一般在方案开头就把这几个定义固定下来,后面所有表格都用同一口径。

3.2 环境清单要细到版本号

环境清单是方案里最不起眼但最不能省的部分。方案给的是双表结构,硬件一张、软件一张:

服务器类型IPCPU内存硬盘操作系统
测试客户端10.1.1.18016 核8G500GWin10
应用服务器10.8.3.19916 核64G500GWin2012
数据库服务器10.8.3.17316 核64G500GWin2012
服务器类型软件配置
测试客户端Win10 SP1 64 位,JMeter 5.1.1 及 agent
应用服务器Win Server 2012 R2,JDK 1.8,Tomcat 8.0,Nginx 1.10
数据库服务器Win Server 2012 R2,Oracle 19c

为什么版本号要写进去?因为压测结论只在特定环境下成立。同样的脚本,JDK 1.8 和更高版本的 GC 表现不一样,Tomcat 的连接池默认值不一样,Oracle 19c 和 11g 的 AWR 报告生成方式也不一样(19c 已经把 ADDM 建议合并进 AWR 脚本,11g 还要单独跑一次)。报告里不写环境,别人拿到数据也没法复现,这份报告就只能自己看。

3.3 监控指标与场景设计

方案把监控指标列成一张表,分四类:数据库 top SQL(用 AWR 或慢日志看耗时最高的语句)、系统整体处理情况(AWR、ADDM)、用户数(线程组线程数)、事务响应时间与成功率(JMeter 聚合报告)、服务端 CPU 与内存(JMeter 加 ServerAgent 插件)。

场景设计遵循由轻到重的顺序:先单接口基准,再混合场景,最后稳定性。基准场景只压一个接口,目的是拿到单笔事务的响应时间基线;混合场景按业务比例编排多个接口,看相互影响;稳定性场景用中等并发长时间运行,主要看内存是否持续增长、连接是否泄漏。方案给了一个业务性能测试案例的结构:把同步执行的请求放进简单控制器,有先后依赖的放进事务控制器,请求前加同步定时器模拟集合点。

这个结构的意义在于,事务控制器会把内部所有请求的耗时合并成一个事务时间,正好对应业务上“提交一次登记”这个动作;简单控制器则只做分组,不改变耗时统计。选错控制器,统计出来的响应时间就是错的。

4. 脚本执行:从控制器编排到服务端监控接入

脚本执行不只是点一下运行。方案里把执行前后的准备工作拆成七步,覆盖脚本组织、结果收集、服务端监控三块,我按实际操作的顺序重排了一遍。

4.1 线程组与控制器编排

脚本按用例命名,一个用例对应一个业务动作。用例内部如果几个请求同步发起,用简单控制器包起来;如果请求之间有先后顺序,用事务控制器。请求执行前加同步定时器,作用是所有线程到齐后再一起发出,模拟真实用户在同一时刻提交,避免线程陆续启动造成的压力分散。

同步定时器的参数有两个:Number of Simulated Users to Group by 填目标并发数,Timeout 填超时毫秒数。超时设置太小,线程等不齐会提前发出,集合点就失效了;设置太大,某个线程卡住会让所有线程一起等。我一般按并发数的 1.5 倍到 2 倍给超时,比如 200 并发给 300 毫秒到 400 毫秒。

脚本里必须挂聚合报告,这是看结果的基本盘:样本数、平均响应时间、90%/95% 线、错误率、吞吐量。方案还提到可以在数据服务端调整 AWR 的快照频率,默认一小时一次,如果场景跑得密,就手工执行一次快照:

-- 手工触发一次 AWR 快照,跑完一个场景就执行一次 exec DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT(); -- 生成 AWR 报告,? 代表 ORACLE_HOME @?\RDBMS\ADMIN\awrrpt.sql;

手工快照的好处是场景和报告一一对应。默认一小时一次快照,一个 20 分钟的场景跑完,快照可能还没生成,你拿到的报告里混着上一个场景的残余数据,瓶颈定位就会被带偏。11g 环境还要补跑addmrpt.sql拿优化建议,19c 不用,这两个脚本已经合并了。

4.2 服务端监控三件套:ServerAgent、PerfMon、jconsole

服务端资源监控分两层。第一层是操作系统层,在应用服务器和数据库服务器上启动 JMeter 的 ServerAgent:Windows 双击startAgent.bat,Linux 执行startAgent.sh,然后确认测试客户端到服务端的 4444 端口是通的。通不了就换工具,比如 nmon,别在这里耗时间。

# Linux 服务端启动 ServerAgent,默认监听 4444 端口 ./startAgent.sh # 从测试客户端验证端口连通性,能通才会在 JMeter 里看到数据 telnet 10.8.3.199 4444

端口通了之后,在 JMeter 里加 PerfMon Metrics Collector 监听器,勾选 CPU、内存、磁盘 I/O 指标。这个监听器是可选插件,要先在插件管理器里装上。采集间隔建议 5 秒一次,太密会额外增加服务端负担,太疏会漏掉瞬时峰值。

第二层是 JVM 层。服务端先开远程 JMX 端口,本地用 jconsole 连IP:端口。jconsole 主要看两个页签:内存页签相当于可视化的 jstat,重点看每次 Full GC 之后内存能不能回落;线程页签相当于可视化的 jstack,遇到线程停顿时看它在等什么。线程长时间停顿的原因无非三类:等外部资源(数据库连接、网络、设备)、死循环、锁等待(活锁或者死锁)。方案里这两句判断非常实用,基本上把 JVM 层排查的入口说清楚了。

4.3 执行时的记录规范

正式跑之前,先确认并发数。场景要多少并发,线程组就设多少,不要为了“多压一点”随意加。跑的过程中,每个场景结束记录三样东西:聚合报告截图、服务端监控曲线截图、数据库侧的 AWR 报告文件名。三样东西按场景编号命名存到同一个目录,写报告时直接引用。

方案里有一句提醒:复杂的场景建议调快 AWR 快照频率,简单的场景可以全部跑完再统一生成报告。这条对时间安排很有用,复杂场景一个接一个跑,中间不留快照,后面的分析就会变成多场景数据混在一起,很难判断是哪个场景造成的压力。

5. 避坑与排查:性能测试中最容易出错的五个环节

这一章按“现象 → 原因 → 解决”整理,都是做这套方案时会真实遇到的问题,提前知道能少走很多弯路。

5.1 压测结果异常好看,其实是数据量不够

现象:并发 200,响应时间 200 毫秒,吞吐量很高,看着很漂亮。原因:库里只有几千条数据,索引全命中,缓存也全命中,和真实生产环境的数据量差两个数量级。解决:压测前确认每张核心表的数据量达到需求规格里写的量级,100 万就是 100 万。数据量不够,压出来的所有数字都只能当功能验证,不能写进报告当性能结论。

5.2 造数成功但数据是脏的

现象:造数跑完了,count 也对得上,后面按业务 ID 关联查询却查不到数据。原因:参数化字段没有保持业务一致,比如请求体里的患者 ID 是随机生成的,但库里根本没有这个患者,接口却返回成功。解决:造数后用业务 SQL 抽样验证,随机取几十条记录,按主外键关联查一遍,确认关联关系成立。参数化不是把所有字段都随机化,主键类字段要来自真实数据源。

5.3 服务端监控拿不到数据

现象:PerfMon 监听器一片空白,聚合报告正常。原因:4444 端口不通,或者服务端 ServerAgent 没启动,或者防火墙拦截。解决:先在客户端 telnet 一下 4444,不通就查防火墙和进程;通但还是没数据,检查 ServerAgent 是否用对系统的启动脚本(bat 和 sh 不能混用),再检查 JMeter 版本和插件版本是否匹配。实在不通就换 nmon,别在工具连通性上无限投入。

5.4 CPU 高不等于有问题,Wait 高才是信号

现象:监控看到 CPU 跑到 80% 以上,第一反应是系统扛不住了。原因:性能测试的目的本来就是压出硬件能力,CPU 高是正常现象。真正要区分的是 CPU 消耗在 User、Sys 还是 Wait。解决:看 CPU 构成。如果是 Wait 占大头,说明 CPU 在等 I/O,多半是大表全表扫描没走索引,去 AWR 里找 top SQL,看执行计划里是不是走了 TABLE ACCESS FULL。如果是 User 高,说明是计算密集,去看业务逻辑有没有低效循环。

5.5 内存不回落,是泄漏最直接的证据

现象:压测中途内存持续上涨,压力撤掉后也不回落。原因:可能是内存泄漏,也可能只是对象还没被回收。解决:看 Full GC 之后的内存曲线。如果每次 Full GC 后占用都没有回到原来水平,且压力撤除一段时间后依旧不恢复,基本可以判定泄漏。判断依据要写成“Full GC 后基线持续抬升”,而不是只看绝对占用值,否则容易误判。

6. 报告与答辩:让压测数据站得住脚的呈现方法

性能测试报告最怕的不是数据不好看,而是数据不可信。一份能过评审、能用于答辩的报告,结构上要能回答四个问题:测了什么、在什么环境测的、结果是什么、瓶颈在哪。方案里报告的口径是:测试目的、测试环境、测试方法、测试结果(吞吐量、响应时间、并发用户数)、瓶颈分析与调优建议,五块内容缺一不可。

呈现上有几个具体做法。结果部分用表格而不是截图堆砌,一个场景一行,列是并发数、平均响应时间、95 线、TPS、错误率,纵向能看出趋势,横向能对比场景。截图放在附录,正文只引用关键的一张,比如内存泄漏的证据曲线,或者 CPU Wait 占比异常的那段监控图。瓶颈分析部分一定要挂上证据链:哪个指标异常 → 对应哪个监控数据 → 定位到哪条 SQL 或哪段代码 → 建议怎么改。只说“数据库存在性能瓶颈”不算分析,说到“EXAM_REQUEST 表的分页查询未走索引,AWR 中该 SQL 占 DB Time 的 42%,建议在登记时间字段加复合索引”才算。

答辩时最常被问的三个问题是:数据量够不够、环境是不是生产同构、结论能不能复现。前两个用环境清单和数据量核对表就能答,第三个要求你把脚本、数据源、监控配置一起交付,别人拿到能重跑出接近的数字。我一般会在报告最后附一份复现说明,写清楚脚本版本、JMeter 版本、造数方式和快照生成步骤。

从那以后我每次做压测,都强制先走一遍“数据量核对 → 端口连通性验证 → 单场景试跑”这三步,哪怕时间再紧也不跳。跳过这三步省下的半小时,后面往往要用一整天来找原因。希望这套流程能帮到你,需要这份完整培训方案的话,可以直接拿去对照着搭自己的性能测试链路。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进