跳转至

JMeter 线程组与场景设计

场景设计错,后面所有调优都是白费。这一篇按“误用现场 → 参数语义 → 四类场景 → 计时器与控制器的真实行为 → 检查清单”展开,环境为 JMeter 5.6.3,插件为 jpgc Standard Set 2.2。

一、四个真实误用现场

1. Ramp-up 填 0,500 线程瞬间打满

线程组:Threads=500,Ramp-Up=0,Loop=10。启动 3 秒后服务端开始 502,jmeter.log 里成片出现:

java.net.ConnectException: Connection refused: connect

报告错误率 43%,业务方直接判定“系统只能扛 200 并发”。实际是 500 个 TCP 连接在一瞬间挤进 accept 队列,内核 somaxconn 溢出,跟业务处理能力没关系。

2. 循环次数选了 Forever,场景停不下来

非 GUI 跑了一夜,第二天 jtl 涨到 80 GB,报告一直生成不了。因为 Loop Count = Forever 且没勾调度器,线程永远不会自然结束。非 GUI 模式必须用 bin/shutdown.sh(优雅)或 bin/stoptest.sh(强制)收尾,直接 Ctrl+C 只会杀掉前台进程,报告目录可能不完整。

3. 把线程数当 TPS 报给业务

“我们压了 500 并发,所以系统能扛 500 TPS。”实测稳态 TPS 只有 180。原因是每个线程一次迭代要发 4 个请求、平均 RT 260ms、思考时间 1s,单线程每秒只能完成 0.79 次迭代。

4. 换台机器打开 jmx 就报类找不到

ERROR o.a.j.JMeter: CannotResolveClassException: kg.apc.jmeter.threads.UltimateThreadGroup

jmx 里用了 jpgc 的 Ultimate Thread Group,新机器没装插件。JMeter 不会提示“缺插件”,只会把这个元件静默丢弃,场景跑出来线程数完全不对。

二、线程组参数的真实语义

参数 真实含义 常见误解
Number of Threads 虚拟用户数(线程数),不是 TPS,也不是并发请求数 当成 TPS
Ramp-Up Period 用多少秒把所有线程启动完,间隔 = Ramp-Up / 线程数 以为是“延迟多少秒开始”
Loop Count 每个线程执行多少轮迭代 以为 Forever 也会自己停
Same user on each iteration 每轮是否复用同一线程上下文(Cookie、变量、缓存) 以为只影响报告里的用户名
Delay Thread creation until needed 线程首次执行时才创建,降低空闲内存 以为等于延迟启动
Scheduler / Duration 到点后请求线程停止,正在跑的采样器会先结束 以为到点立刻断
Startup delay 线程组启动前等待的秒数 与 Ramp-Up 混用

几个必须记住的换算:

线程启动速率 = Number of Threads / Ramp-Up   (个/秒)
单线程迭代周期 = 事务耗时 + 思考时间 + Pacing
单线程 TPS ≈ 1 / 迭代周期
总 TPS ≈ 线程数 × 单线程 TPS

Ramp-Up=0 表示“立刻全部启动”,不是“不启动”。Ramp-Up 小于单次迭代耗时,第一批线程还没跑完、最后一批已经进来,实际并发会堆到接近线程总数,这时的曲线是“一波流”,不能用来判断稳态容量。

三、线程数、并发、TPS 三者不是一回事

用利特尔法则串起来:

并发请求数 = TPS × 平均响应时间(s)
线程数 = 并发请求数 + 处于思考时间/等待中的线程

算例对比:

线程数 平均 RT 思考时间 单线程周期 理论 TPS
1 100 ms 0 0.1 s 10
100 200 ms 0 0.2 s 500
100 200 ms 1 s 1.2 s 83
500 200 ms 1 s 1.2 s 417

所以“500 线程”在带 1 秒思考时间的场景里只对应 417 TPS 的理论上限,实际还会被服务端、数据库、压测机打折。报容量时必须同时给出:线程数、思考时间、Pacing、实测 TPS、RT 分位数,缺一项结论就不可复现。

四、Ramp-Up 与 TPS 曲线的关系

Ramp-Up 期间并发从 0 线性升到 N,TPS 也近似线性上升。设稳态 TPS 为 S,Ramp-Up 为 R,总时长 D,则报告里的平均 TPS:

平均 TPS ≈ S × (D - R/2) / D

算例:稳态 500 TPS,Ramp-Up 600 s,Duration 1800 s。

平均 TPS ≈ 500 × (1800 - 300) / 1800 ≈ 416.7

聚合报告显示的 417 不是系统能力,是被 Ramp-Up 摊薄的结果。判定方法:

  • 在“Transactions per Second”图里截取平台段(前后各去掉 Ramp-Up 和下降段)作为稳态 TPS。
  • Ramp-Up 建议 ≥ 单次迭代耗时的 2 倍,且 ≥ 目标线程数 / 每秒可启动线程数(普通 PC 约 20–50 个/秒)。
  • 稳定性场景的 Ramp-Up 单独计算,不占总时长比例过大;8 小时稳定性测试用 10 分钟爬坡是合理的,30 分钟爬坡就会污染统计。

五、四类场景的参数设计表

场景 目的 线程数 Ramp-Up 持续 循环 通过标准
基准 单用户基线、脚本正确性 1 1 s 5–10 分钟 20 全链路 0 错误,拿到单请求 RT
容量/梯度 找拐点、算单机容量 50→100→200→400 每档 60–120 s 每档 10 分钟 由调度器控制 TPS 线性增长,P95 不超阈值
稳定性 泄漏、连接池、GC 目标的 70%–80% 10 分钟 8–24 小时 Forever + Duration 错误率 < 0.1%,内存无单向增长
尖峰 秒杀、开抢、突增流量 目标峰值 5–10 s 3–5 分钟 3–5 轮 不雪崩,限流生效,RT 可恢复

梯度场景不要用一个线程组反复改参数,直接用 jpgc 的 Stepping Thread Group 一次跑完,报告里能按时间段切片。

六、Synchronizing Timer:真正的瞬时并发

普通线程组即使 Ramp-Up 很小,线程也是陆续启动的。要让 200 个线程“同一毫秒”发请求,只能靠集合点。

字段 语义 坑
Number of Simulated Users to Group by 每凑够多少个线程释放一次;填 0 表示当前线程组的全部线程 填得比线程数大 → 永远等不齐
Timeout in milliseconds 等待上限,0 表示无限等 生产脚本必须设,否则一个线程卡死整组
<SyncTimer guiclass="TestBeanGUI" testclass="SyncTimer"
           testname="Synchronizing Timer - buy" enabled="true">
  <intProp name="groupSize">200</intProp>
  <longProp name="timeoutInMs">30000</longProp>
</SyncTimer>

使用要点:

  • 放在事务开始之前,不要放在事务控制器内部,否则集合等待时间会被计入事务 RT。
  • 集合点制造的是瞬时并发峰值,不代表 TPS。1000 线程集合一次只产生一波请求,Hits per Second 图上是一个尖刺,不是持续吞吐。
  • 集合点必须放在 Loop 内层时,每一轮都会重新集合;放在 Loop 外层只会集合一次。
  • 线程数不能被 groupSize 整除时,最后一批会等到 Timeout 才释放,报告尾部会出现一批超时样本。

七、恒定吞吐:CTT 与 PTT 的区别

7.1 Constant Throughput Timer

最容易踩的坑是单位:Target throughput 是“每分钟样本数”,不是每秒。想要 10 TPS 就填 600。

字段 取值 说明
Target throughput 600 每分钟 600 个采样器 = 10 TPS
Calculate Throughput based on All active threads (shared) 多线程共享一个目标;选 This thread only 则是每线程各 600/分钟

它靠“请求前 sleep”限速,公式近似:

间隔 = 60000 / 每分钟目标 - 上次请求已耗时

因此有三个硬限制:

  1. 并发不足时达不到目标(它只能减速,不能加速)。
  2. 响应时间抖动大时,实际 TPS 会在目标上下漂移,实测偏差 5%–15%。
  3. 它不控制请求的瞬时分布,短窗口内的 TPS 仍然不均匀。

7.2 Precise Throughput Timer

5.x 内置,不用装插件。关键字段是 Target throughput (in samples per minute)、Throughput period (seconds)、Test duration (seconds)。它按批次计算每个样本的预定发送时刻,长时间运行时更接近目标,且能配置成随机到达(近似泊松分布),比 CTT 更适合“模拟真实用户不均匀到达”。

维度 Constant Throughput Timer Precise Throughput Timer
单位 样本/分钟 样本/分钟(配合周期与总时长)
精度 粗,受 RT 影响 细,按计划时刻投放
分布 均匀减速 可均匀也可随机
必需参数 目标值 + 计算基准 目标值 + 周期 + 测试时长
适用 快速限速、稳定性场景 精确复现业务 TPS 曲线

配置示例(目标 12 TPS,跑 30 分钟):

Target throughput (in samples per minute) = 720
Throughput period (seconds)              = 60
Test duration (seconds)                  = 1800

两个 Timer 都只对同作用域内的采样器生效,挂在事务控制器外才能限制其内部所有请求。

八、吞吐量控制器:把业务比例做进脚本

模式 行为 注意
Percent Executions 按百分比随机决定是否进入 每个线程独立计算,短时间比例偏差大
Total Executions 执行满指定次数后不再进入 多线程下计数口径要实测校准

混合场景配比示例(浏览 60%、加购 20%、下单 15%、支付 5%):

Thread Group  200 threads, ramp-up 120s, duration 1800s
├─ Transaction Controller "browse"  ← Throughput Controller 60%
├─ Transaction Controller "cart"    ← Throughput Controller 20%
├─ Transaction Controller "order"   ← Throughput Controller 15%
└─ Transaction Controller "pay"     ← Throughput Controller 5%

注意百分比是“进入次数”的比例,不是“成功请求数”的比例。下单接口错误率高时,实际成功的业务比例会明显偏离设计值,报告里要按成功样本重新算。

九、事务控制器:统计口径的分水岭

选项 作用 误用后果
Generate parent sample 报告只显示事务总耗时,子请求仍记录在 jtl 不勾选时报告里全是零散接口,看不到端到端 RT
Include duration of timer and pre/post processors 事务时间包含 Timer、前后置处理器 勾选后思考时间被算进 RT,P95 虚高
嵌套事务 父子都计入样本 聚合报告样本数翻倍,TPS 虚高

判定标准:性能报告里的 TPS 必须和业务口径一致。一个下单事务包含 4 个接口,如果父子都统计,报告 TPS 会是真实下单数的 5 倍。压测前先确认“1 TPS 指的是 1 个事务还是 1 个 HTTP 请求”。

事务名里不要带 ${__threadNum} 之类的变量,否则聚合报告会出现几百行不同名字的事务,无法汇总。

十、setUp / tearDown 线程组

  • setUp 在每个测试计划里最多一个,先于所有普通线程组执行;tearDown 最后执行。
  • 典型用途:setUp 登录拿全局 token、准备测试数据;tearDown 清理订单、删除临时用户。
  • setUp 里的变量不会传给普通线程组(线程隔离)。必须落文件、写属性或写数据库:
${__setProperty(global_token,${token},)}
${__P(global_token)}
  • setUp 失败不会自动终止主线程组,JMeter 仍会继续跑。必须在主线程组开头加断言或 If Controller 检查 token 是否存在,否则整场压测都在用空 token。
  • tearDown 在线程组被强制停止时可能不执行,清理逻辑不要只依赖它。

十一、jpgc 阶梯线程组

安装方式:Options → Plugins Manager → Available Plugins → 搜索 jpgc - Standard Set → 安装后重启 JMeter。

11.1 Stepping Thread Group

字段 含义
This group will start 最终线程总数
First wait for 第一批线程启动前等待
Then start 首批启动多少线程
Next, add 每阶梯增多少线程
Threads every 每阶梯间隔多久
Using ramp-up 每批线程用多久启动完
Then hold load for 到顶后保持多久
Finally, stop 每批停止多少线程 / 间隔

这套参数天然对应“梯度加压找拐点”,比手改普通线程组五次可靠。

11.2 Ultimate Thread Group

每一行是一条独立的线程曲线(Start Threads、Initial Delay、Startup Time、Hold Load For、Shutdown Time),可以拼出“上升—保持—下降—再上升”的波浪形,适合模拟一天内的流量波动。行与行之间是叠加关系,不是替换关系。

打开 jmx 报 CannotResolveClassException: kg.apc.jmeter.threads.UltimateThreadGroup,只有一个原因:目标机器没装 jpgc 或版本不兼容。不要靠手改 XML 绕过,装了插件再打开。

十二、常见误用表

误用 直接后果 正确做法
Ramp-Up = 0 瞬时连接风暴,502/Connection refused 按 20–50 线程/秒设置
Loop = Forever 且无 Duration 场景不结束,jtl 无限增长 Forever 必须配 Scheduler
线程数当 TPS 容量结论虚高 2–5 倍 用 TPS = 线程数 / 迭代周期 折算
所有场景共用一个线程组 参数互相污染 每个场景独立 jmx 或独立线程组
集合点放在事务内 RT 包含集合等待,P95 失真 放在事务开始前
CTT 目标填 10(以为是 TPS) 实际 10 样本/分钟,压了个寂寞 填 600
事务控制器父子都统计 TPS 翻倍 明确统计口径,报告注明
稳定性测试不设 Ramp-Up 前 10 分钟全是错误,污染错误率 单独爬坡 10 分钟
压测机上开着查看结果树 压测机 CPU 先满,TPS 上不去 非 GUI + 关闭监听器
用 GUI 跑正式压测 GUI 自身开销大,结果不可信 jmeter -n -t ... -l ... -e -o ...

非 GUI 启动命令:

jmeter -n -t scenario.jmx -l result.jtl -e -o report

-o 指定的目录必须为空,否则启动直接失败;每次重跑前先删目录,不要复用上一次的 report。

十三、场景设计检查清单

  • 场景类型明确(基准/容量/稳定性/尖峰),不是“压一下看看”
  • 线程数由目标 TPS 与迭代周期反算,不是拍脑袋
  • Ramp-Up ≥ 单次迭代耗时 × 2,且未造成启动瞬间错误
  • Forever 场景都配了 Duration,并准备了 shutdown 脚本
  • 思考时间、Pacing 与生产一致,报告里写明了取值
  • 集合点只用在需要瞬时并发的接口,且设置了 Timeout
  • 限速用 CTT/PTT 时确认单位是“每分钟”
  • 事务控制器统计口径与业务一致,报告注明 1 TPS 的定义
  • setUp 产物通过属性/文件传递,并有存在性校验
  • 正式压测用非 GUI,jtl 与 report 目录独立,环境版本已记录

相关阅读