ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

JMeter性能测试实战:百万造数、监控调优与瓶颈定位

JMeter性能测试实战:百万造数、监控调优与瓶颈定位

简介:《性能测试-Jmeter.pptx》是一份围绕JMeter展开的性能测试培训课件,面向测试工程师、开发与运维人员,帮助其建立从数据准备到报告输出的完整性能测试流程认知。资源包共1个PPTX文件,约12.48MB,以五个章节组织内容,适合培训授课或自学查阅。课件重点覆盖数据制造、性能测试方案设计、脚本执行、性能监控与调优、性能测试报告编写:既对比JMeter参数化脚本与存储过程/JAVA造数的效率与适用场景,也说明测试环境软硬件清单、同步定时器与事务控制器、ServerAgent与PerfMon监控、AWR/慢日志分析等实操要点。其中包含抓包参数化、多线程循环造数、Jenkins持续运行等思路,可帮助读者理解高负载下系统稳定性验证的关键环节。目前已有1150人学习,适合需要系统梳理JMeter性能测试方法的初中级测试人员参考。

1. 造数据这件事,决定了一次性能测试能不能落地

手上这份 JMeter 性能测试培训材料,切入点很实在:不是从"什么是并发"讲起,而是从"新建登记模块要 100 万条铺底数据"这种真实诉求开始。它把一次完整的性能测试拆成五段——造数据、写方案、执行脚本、监控调优、出报告,每一段都给到了可照着做的动作,比如用 Fiddler 抓登记保存接口再参数化回放、20 线程循环 50000 次凑够百万数据、AWR 手工打快照、JConsole 远程连服务端看内存和线程。适合已经能写几个 HTTP 请求、但一放到真实项目里就卡在数据量、并发模型和瓶颈定位上的同学;也适合需要把性能测试流程固化成团队规范的人。下面按我自己复现的顺序,把每段的参数和坑摊开讲。

2. 造数据的两种路子:HTTP 脚本回放和直插数据库怎么选

数据准备是整条链路上最耗时间、也最容易被压缩的一环。培训材料里把需求写得很清楚:新建登记模块 100 万条,登记保存模块 100 万条,涉及EXAM_REQUEST、EXAM_PATIENTVISIT这类表。数据分三类——初始化数据(字典、机构、用户)、铺底数据(历史业务数据,用来把表撑大、把索引撑出真实的选择性)、参数化数据(每轮请求要变的字段)。三类里前两类是一次性的,第三类是脚本运行时持续消耗的,混在一起想就会乱。

2.1 选型:为什么最后落在 JMeter 脚本而不是直插数据库

两条路的差别不只是速度,而是"谁对数据正确性负责"。直插方案把责任放在写 SQL 的人身上,脚本方案把责任交给业务代码。培训里的取舍是:对系统表之间的关系不熟、时间又比较充裕,所以选 HTTP 回放。

维度JMeter 参数化脚本回放存储过程 / Java 代码直插
表关联关系不用关心,服务端自己处理必须逐条理清主外键、状态机
数据真实性走完整业务链路,单号、状态、时间戳都由服务端生成只保证写进去的字段对,衍生字段靠手填
造数速度约 2 万~5 万条/小时约 10 万~20 万条/小时
出错代价单条失败,跳过重跑即可一条 SQL 写错,整批脏数据,得清表重来
前置成本抓包 + 参数化 + 调试读表结构 + 写关联 SQL + 验数据
适合场景表结构不熟、要真实分布、时间充裕数据量极大、关系清晰、需要反复重建

百万级数据按 3 万条/小时算要 30 多个小时,这就是为什么材料里强调把脚本丢到 Jenkins 上持续跑,而不是本地开着电脑熬。选直插之前先问自己一句:这条 SQL 写错了,我能不能在半小时内把脏数据摘干净?答不上来就用回放。

2.2 抓包与参数化:把接口参数切成可变和不可变两份

第一步是用抓包工具抓登记保存接口,重点是完整请求体,包括 Header 里的 token、Content-Type,以及 Body 里每一个字段。抓下来之后做一次分类:不变的(机构编码、检查项目编码、医生工号池)写成 CSV 或用户定义的变量;必须变的(患者姓名、身份证号、就诊号、申请时间)走参数化;服务端返回才有的(申请单 ID、流水号)用关联提取,别硬编。

身份证号、就诊号这类字段最容易翻车——用随机数生成,几千个线程跑几十万次必然撞唯一索引。我的做法是用序号保证唯一,随机只用在业务上允许重复的字段。

import csv, random from datetime import datetime, timedelta # 字段顺序必须和 JMeter 里 CSV Data Set Config 的 Variable Names 完全一致 FIELDS = ["patient_name", "id_card", "visit_no", "exam_item", "req_time", "req_doctor"] def build_id_card(seq): # 用自增序号兜底唯一性,随机只放在姓名、项目、时间上 return f"3101{seq:012d}" with open("exam_register.csv", "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(FIELDS) for i in range(1, 20001): writer.writerow([ f"患者{i:06d}", build_id_card(i), f"V{datetime.now():%Y%m%d}{i:06d}", random.choice(["CT", "MR", "DR", "US"]), # 申请时间散布到近一年,避免全挤在同一天,查询分布才像真的 (datetime.now() - timedelta(days=random.randint(0, 365))).strftime("%Y-%m-%d %H:%M:%S"), f"医生{random.randint(1, 200):03d}", ])

这段脚本产出 2 万行基础数据池,不是 100 万行——JMeter 的 CSV Data Set Config 默认循环读取,2 万行 × 50 轮就是 100 万次请求,文件小、加载快。参数说明:build_id_card里的seq决定唯一性边界,如果目标表对身份证有校验位要求,这里要换成带校验位的生成逻辑;req_time的散布范围要覆盖被测查询的最长回溯窗口,否则索引选择性失真,压出来的慢 SQL 全是假的。

CSV Data Set Config 上还有两个参数值得改:Recycle on EOF 设 true(循环读),Stop thread on EOF 设 false;Sharing mode 选 All threads(多线程共享一份,总量可控)还是 Current thread(每个线程一份,容易提前读完),取决于你要的是"总量 100 万"还是"每线程 100 万"。

2.3 小并发试跑与批量放量:先 10 线程验证,再 20 线程 × 50000 次

材料里的第三步是关键的自检环节:参数化完先设 10 个线程跑一次,看是不是每条都成功。这一步省掉,等到跑了 8 小时发现成功率 30%,血泪经验就是这么来的。

试跑阶段看三样:聚合报告的 Err 是否为 0、响应体里业务 code 是不是成功(HTTP 200 但业务失败很常见)、数据库里随机抽 10 条核对字段是否被服务端正确填充。三样都过,才进入放量。

# 非 GUI 模式造数:-n 不启界面,-t 指定 jmx,-l 结果文件,-j 运行日志 jmeter -n -t exam_register.jmx \ -Jthreads=20 \ -Jloops=50000 \ -l data_build_$(date +%Y%m%d%H%M).jtl \ -j jmeter_build.log

脚本里线程组和循环控制器要用函数占位,才能被命令行覆盖:线程数填${__P(threads,10)},循环次数填${__P(loops,100)}。这样同一份 jmx 在本地调试用 10×1,上 Jenkins 用 20×50000,不用改脚本。-l的结果文件按时间戳命名,是为了第二次重跑不覆盖第一次的记录——造数失败重跑时,第一份 jtl 往往是唯一能证明"跑到哪一步断的"证据。

20 × 50000 = 100 万次请求,按 3 万条/小时估算要 30 小时以上,所以放 Jenkins 上定时跑,本地机器该关就关。跑之前确认 Ramp-up 设成合理值(比如 20 线程 20 秒),别一瞬间全打上去,服务端的连接池会先被打爆,造出来的失败和性能无关。

作为对照,直插方案的存储过程长这样,理解它才能理解为什么它快、也为什么它危险:

-- 直插方案:每轮插 1000 条后提交,控制 undo 增长;not exists 防重复 create or replace procedure p_build_exam_data(p_batch in number default 1000) as begin for b in 1 .. p_batch loop insert into exam_request(id, visit_id, item_code, req_time, status) select sys_guid(), v.visit_id, 'CT', sysdate - dbms_random.value(0, 365), '1' from exam_patientvisit v where rownum <= 1000 and not exists (select 1 from exam_request r where r.visit_id = v.visit_id); commit; end loop; end;

p_batch控制轮次,一轮 1000 条后提交,避免一次大事务把回滚段撑满;dbms_random.value(0,365)把申请时间散到一年内;not exists防止重复跑脚本时插出重复就诊记录。它快,是因为绕过了服务端的参数校验、状态流转、消息落库;它危险,也是因为绕过了这些——status直接写 '1',跟真实业务里走完审核流的状态不是一回事,用它造出来的铺底数据去压测,慢 SQL 的形态会和线上不一致。

3. 性能测试方案:需求规格、环境清单、监控指标三张表怎么填

造完数据,下一件事不是写脚本,是写方案。方案的作用是把"系统要快"这种口头要求,翻译成一组可复现、可判定的数字,并且提前把环境、指标、采集方式钉死。材料里把方案拆成四块:需求规格、环境清单、测试方法与监控工具、业务场景案例。

3.1 需求规格:把"要快"翻译成可判定的数字

需求规格一般由项目组提供,但拿到的常常是"支持 500 并发、响应时间 3 秒内"这种半成品。合格的做法是把它补成一张表,每一行都要能被脚本和监控验证:

指标写法示例判定方式
并发用户数登记保存 50 并发,登记查询 100 并发线程组线程数
响应时间95% 事务响应时间 ≤ 2s,最大 ≤ 5s聚合报告 90%/95% 行
吞吐量登记保存 TPS ≥ 30聚合报告 Throughput
成功率Err = 0聚合报告 Error %
持续时间单场景稳态运行 30 分钟线程组调度器
数据量铺底 100 万,参数化池 2 万造数结果核对

"3 秒内"和"95% 在 2 秒内"是两件事,前者允许 5% 的请求慢到不可用,后者不允许。方案阶段把这个歧义问清楚,比压完再解释便宜得多。持续时间也要写死:只跑 3 分钟拿到的是预热曲线,JVM 还没 JIT 完,数据库缓存还没热,测出来的数字没人敢签字。

3.2 环境清单:客户端、应用、数据库三段式分开列

环境信息最忌讳写成一段散文。硬件一张表、软件一张表,三台机器的 IP、核数、内存、磁盘、操作系统逐项列,后面报告里出问题才能对得上号。

服务器类型IPCPU内存磁盘系统
测试客户端10.0.0.1116 核8G500GWindows 10
应用服务器10.0.0.2116 核64G500GWindows Server 2012 R2
数据库服务器10.0.0.3116 核64G500GWindows Server 2012 R2
服务器类型软件配置
测试客户端JMeter 5.1.1 + ServerAgent 客户端
应用服务器JDK 1.8、Tomcat 8.0、Nginx 1.10
数据库服务器Oracle 19c

版本号必须写。JMeter 5.1.1 和更高版本在 PerfMon 插件、JSON 提取器的默认行为上有差异,客户端和服务端版本不一致时,排查会很玄学。压测机和被测机放在同一网段也是常识:隔着防火墙跑,测出来的是网络设备,不是业务系统。

3.3 监控指标与采集方式:从 top sql 到事务成功率

指标表要带"监测方式"这一列,否则压测现场没人知道该开哪个窗口。材料里给出的组合是这样:

指标参考值/说明采集方式
并发用户数线程组线程数JMeter
事务响应时间每笔事务平均响应时间变化JMeter 聚合报告
事务成功率Err = 0 视为 100%JMeter 聚合报告
CPU 使用率服务端整体 CPUServerAgent + PerfMon
内存使用率服务端物理内存ServerAgent + PerfMon
数据库 top sql耗时、消耗资源最多的 SQLAWR
系统整体处理情况快照区间内的负载画像AWR / ADDM

数据库侧的口径要提前定:Oracle 走 AWR,MySQL 走慢查询日志加performance_schema。AWR 默认一小时一次自动快照,短场景根本覆盖不到,所以压测期间要手工打点,这一点放到第 4 章说。

3.4 场景设计:把业务操作映射成线程组结构

场景设计不是把所有接口塞进一个线程组,而是按业务比例拆。材料里举的例子是登记类业务:登记保存、登记查询、影像上传按 5:3:2 的比例分配线程,每个场景一条独立的线程组,各自带同步定时器和聚合报告。

拆场景的判断标准是"能不能单独出结论"。混在一个线程组里,查询慢了你不知道是查询本身慢,还是被保存的写入锁住了。拆开之后每个场景跑一轮,拿到的是可归因的数据;再叠加一轮混合场景,验证的是相互影响。两轮都要跑,只跑混合场景,瓶颈定位会变成猜谜。

方案写完,最好让开发、DBA、运维各看一遍环境表和指标表——压测现场最怕的是"这台机器不是最新的那台",后悔药没地方买。

4. 脚本执行:控制器、同步定时器与三处监控的接线方式

脚本执行这一段,材料给的是云影像系统的登记场景,七个动作按顺序排:命名、控制器选型、聚合报告、服务端 Agent、Web 容器监控、JConsole 连接、按场景执行并记录。顺序本身就是经验——监控没接好就开压,等于闭着眼睛开车。

4.1 命名与控制器:用例化命名,同步用简单控制器、有序用事务控制器

脚本命名按用例来,比如"登记保存_50并发_稳态30min",好处是 JTL 文件、报告截图、AWR 快照三者的名字能对上。树结构里的控制器选型看请求之间的关系:多个接口并发发起、互不依赖,用简单控制器把它们装在一起;接口之间有先后顺序、需要整体算一笔事务响应时间,用事务控制器,它会把这组请求的耗时合并统计。

事务控制器的 Generate parent sample 勾上之后,聚合报告里出现的是一行合并事务,而不是每个接口一行。想让报告里既有事务整体耗时、又能下钻到单个接口,就复制一份事务控制器、不勾这个选项,跑两次——或者干脆在报告里分开列,别在一个控制器上纠结。

请求执行前加同步定时器,模拟集合点。这一点在真实业务里成立:50 个用户同一秒点保存,才有并发的意义;不加集合点,JMeter 线程启动后各跑各的,实际到达服务端的并发可能只有个位数。

4.2 集合点:同步定时器的线程数与超时怎么定

同步定时器的两个参数要配对:Number of Simulated Users to Group by 填线程组线程数(比如 50),Timeout in milliseconds 填 5000 左右。前一个决定攒够多少线程才一起放行,后一个决定等不到就超时放行。

超时值不填是经典的翻车点:只要有一个线程卡住没到集合点,其余线程会一直阻塞,脚本看着在跑,实际早就不发请求了,压测结果是一条平线。填了超时,最坏情况是退化成非同步执行,至少能跑完。另外线程组如果启用了调度器、线程会分批启动,集合点数字要和同时活跃的线程数一致,否则永远凑不齐。

4.3 服务端监控:ServerAgent 的 4444 端口与 PerfMon 插件

服务端装 ServerAgent,Windows 双击startAgent.bat,Linux 跑startAgent.sh,默认监听 4444。压测机上装 PerfMon Metrics Collector 插件(JMeter 的可选插件,需要单独下载),添加 CPU、Memory、Disk I/O 三类指标,Host 填服务端 IP,Port 填 4444。

# 服务端启动 Agent,关掉 UDP 只留 TCP,防火墙少放行一条规则 cd /opt/jmeter/ServerAgent-2.2.3 nohup ./startAgent.sh --udp-port 0 --tcp-port 4444 > agent.log 2>&1 & # 压测机先验证端口通不通,再谈曲线有没有数据 nc -zv 10.0.0.21 4444

--udp-port 0表示禁用 UDP 采集,只走 TCP,企业网里 UDP 被拦的概率比 TCP 高;nohup加日志重定向是为了在断开 SSH 后 Agent 还活着,日志里能看到采集是否正常启动。端口不通时不要急着换工具,先确认服务端防火墙和云上的安全组两层都放行了——材料里提到网络不通就改用 nmon 之类的工具,其实多数情况是安全组没开。

4.4 JVM 侧:JConsole 远程连接,重点看内存和线程两个页签

中间件层面的问题,PerfMon 看不到。Tomcat 的内存泄漏、线程阻塞、死锁,要靠 JConsole 这类 JMX 工具。服务端启动参数里开远程端口:

# 加在 Tomcat 的 JAVA_OPTS 里,测试环境才这么开,生产不要关认证 -Dcom.sun.management.jmxremote \ -Dcom.sun.management.jmxremote.port=9004 \ -Dcom.sun.management.jmxremote.authenticate=false \ -Dcom.sun.management.jmxremote.ssl=false \ -Djava.rmi.server.hostname=10.0.0.21

java.rmi.server.hostname必须写服务端的真实 IP,不写的话 JConsole 会拿到容器或虚拟网卡的内网地址,连接超时但端口测试是通的,这种问题能查半天。压测机上打开 jconsole,输入10.0.0.21:9004连接。

内存页签相当于可视化的jstat,看的是各代内存和 GC 次数,判断有没有内存泄漏的标准是"压力撤除后能否回落":每次 Full GC 后占用都回不到原来的水平,压力停了半小时还在高位,基本可以定性泄漏。线程页签相当于可视化的jstack,看线程数与状态分布,长时间停顿的线程通常卡在三处——等外部资源(数据库连接、网络、设备)、死循环、锁等待(活锁或死锁)。点开具体线程能看到堆栈,一眼就能看出是在等连接池还是在自旋。

4.5 压测节奏:一个场景一次快照,别指望自动快照

脚本跑起来之后,数据库侧要同步打点。AWR 默认一小时一次快照,一个 30 分钟的场景可能一次都覆盖不到,报告里就是空白。材料里的做法是手工打:

-- 场景开始前打一次,结束后再打一次,两次快照之间就是完整的场景区间 exec dbms_workload_repository.create_snapshot(); -- 生成 AWR 报告,? 代表 ORACLE_HOME @?/rdbms/admin/awrrpt.sql

场景复杂就每个场景打一对快照,场景简单可以整轮压完打一对。Oracle 11g 想拿到 SQL 优化建议还要额外跑addmrpt.sql,19c 已经把两个脚本合并,这一点在跨版本复用时要注意,别在 19c 上找 ADDM 脚本找半天。

执行期间的结果记录,材料要求截图加数据一起存:聚合报告的 Throughput、Average、90%/95%、Error%,PerfMon 的 CPU/内存曲线,JConsole 的内存和线程页签。截图要带时间戳,否则事后对不上是哪个场景。

5. 避坑与排查:造数、集合点、监控里最容易翻车的五处

下面五条都是复现这套流程时真会碰上的,按"看到什么现象、根子在哪、怎么处理"记。

5.1 造数跑了一夜,数据库里一条没多

现象:JTL 里请求全是 200,成功率 100%,但目标表 count 不变。

原因:抓包抓到的是页面提交前的校验接口或草稿保存接口,不是最终落库的那个;也有可能是请求头里少了 token,服务端返回 200 但 body 里是业务失败码。

解决:抓包时确认接口 URL 和页面上的"提交/保存"动作一一对应;在 JMeter 里加 JSON 断言或响应断言,把业务 code 校验上,别只看 HTTP 状态码。断言加上之后,试跑 10 条就能暴露问题,不用等一夜。

5.2 数据量翻倍或者只有预期的一半

现象:预期 100 万条,跑完发现 200 万,或者 50 万。

原因:线程数 × 循环次数是乘法关系,改了线程组又改了循环控制器,两个数都生效;或者 CSV 提前读完,Stop thread on EOF 设了 true,线程提前退出。

解决:造数前先算一遍 20 × 50000 = 100 万,用一个变量控制总量;CSV Data Set Config 的 Recycle on EOF 设 true、Stop thread on EOF 设 false;跑之前先用 2 线程 × 5 次验证乘法关系,别直接上百万。

5.3 脚本看着在跑,服务端压力却掉到零

现象:JMeter 显示线程全在运行,聚合报告的 Throughput 从中段开始一路下滑到接近 0,服务端 CPU 也躺平。

原因:同步定时器的 Timeout 没填,某个线程因为业务异常提前退出,剩下的线程在集合点永远等不齐,整组阻塞。

解决:Timeout 填 3000~5000ms,让集合点在异常情况下能退化放行;同时在请求上加响应断言,异常请求早点暴露,不要靠集合点卡死来发现。

5.4 PerfMon 曲线一条直线,CPU 永远是 0

现象:PerfMon Metrics Collector 加了服务端 IP,图里没数据或者全是 0。

原因:4444 端口不通,可能是服务端防火墙、云安全组、Agent 没起来,也可能是压测机装了插件但没重启 JMeter。

解决:先在压测机上nc -zv 服务端IP 4444验证连通,再看服务端agent.log有没有启动成功;插件装完必须重启 JMeter 才生效。网络确实不通时再考虑换 nmon 这类离线采集工具,但先排掉端口问题,绝大多数情况就是它。

5.5 AWR 报告里没有压测区间的数据

现象:跑完 30 分钟场景,生成 AWR 报告,选择时间段时发现最近的快照是两小时前的。

原因:AWR 默认 1 小时自动快照一次,短场景落在两次快照之间,报告里自然没有。

解决:压测前手工exec dbms_workload_repository.create_snapshot();,结束后再打一次,报告里选这两个快照 ID 的区间。复杂场景每个场景打一对,快照 ID 记在场景记录表里,出报告时不用猜。

6. 出报告与定位瓶颈:把聚合报告、AWR 和 JConsole 读成一句结论

报告的价值不在于把截图贴满,而在于能指着某一行说"这儿慢,原因是这个"。我一般按三条线交叉看:聚合报告看现象,AWR 看数据库,JConsole 看 JVM。

聚合报告里先看 Error%,不为 0 就先解决错误,别急着分析响应时间;再看 Throughput 随并发变化的曲线,并发加了一倍、TPS 没涨甚至跌了,说明已经到了拐点;最后看 90%/95% 和 Average 的差距,平均值 800ms 但 95% 是 4s,说明有一批请求在排队,瓶颈在资源争用而不是单次计算。

日志是最直接的黑匣子,几个关键字能快速定位方向:

日志关键字指向下一步
OutOfMemoryError / Cannot allocate memoryJVM 内存溢出JConsole 看老年代,查大对象
SQLException数据库语句执行异常AWR 找 top sql,看执行计划
Connection closed / refused连接被关闭或拒绝查连接池上限、服务端 backlog
大量线程 BLOCKED锁等待jstack 抓堆栈,找持锁线程

CPU 高不一定是问题,压测本来就要把资源吃满;但长期高于 80% 就要区分消耗在 User、Sys 还是 Wait。Wait 高而 User 低,通常是在等 I/O 或等锁,比如大表没走索引的全表扫描,CPU 看着忙其实在等磁盘。

数据库侧对着 AWR 看两段:SQL ordered by Elapsed Time 找耗时最长的,SQL ordered by Gets 找逻辑读最多的,两者常常不是同一条。内存泄漏的判定只看一件事——压力撤除后内存能不能回落,回不去就是泄漏,别用"GC 之后降了一点"安慰自己。

从那以后我每跑一个场景,都先把快照 ID、JTL 文件、PerfMon 导出图按场景号命名存进同一个目录,再动笔写报告;报告里每一条结论后面挂一个文件名,谁质疑就翻证据。这个习惯省下的扯皮时间,比压测本身多得多。希望帮到你。

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

返回列表