news 2026/10/20 8:15:26

JMeter Arrivals Thread Group:基于到达率模型的容量压测实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JMeter Arrivals Thread Group:基于到达率模型的容量压测实战解析

Arrivals Thread Group 这个插件,我第一次在负载模型里认真用它,是在一个计划做“每秒 800 个下单请求”容量评估的接口上。当时用标准线程组跑了十几轮,测试结果波动非常大:明明线程数没变,后端性能稍差一点,实际打到接口上的请求量就跟着掉下去,根本没法判断容量天花板是多少。后来换了 Arrivals Thread Group,同样是压“每秒到达量”,结果稳定很多,数据可比性一下子上来了。

很多 JMeter 使用者看到“Arrivals”这个词会有点懵,容易把它跟“线程数”“并发用户数”混在一起。其实它的负载模型跟标准线程组有本质区别:标准线程组关心的是“同一时刻有多少用户在线”,Arrivals Thread Group 关心的是“每秒钟有多少业务请求到达系统”。这两种指标在性能测试里缺一不可,尤其当你需要按真实业务量级做容量评估、突发流量模拟或全链路压测时,Arrivals 模型往往比线程组模型更贴近现实。下面我就从负载模型差异、插件核心原理、源码关键机制到实际压测中的配置方法,把这套东西完整拆开聊聊。

1. 先用“到达率模型”理解一件事:为什么标准线程组压不准量级

1.1 线程数代表并发,不代表每秒请求量

标准 Thread Group 里的“线程数”,本质上描述的是:压测工具最多允许多少个虚拟用户同时处于活动状态。每个线程在跑一遍脚本之后,会立刻开始下一轮循环。所以最终打到服务器上的每秒请求量,并不是由线程数直接决定的,而是由“线程数 ÷ 单个请求平均耗时”间接算出来的。

举个例子:我用 100 个线程去压一个接口,如果这个接口平均响应时间 50ms,那么理论上能跑出约 2000 次/秒的请求;但如果系统当前负载高,响应时间涨到 500ms,那 100 个线程实际只能产生约 200 次/秒的请求。问题就在这:压测过程中系统响应时间会随负载变化,所以标准线程组产出的实际负载是一条随系统状态漂移的曲线,而不是一个我们提前设定好的固定到达率。

在做容量测试时,我们经常接到类似“这个活动预计每秒有 1200 个下单请求”这种需求。换成标准线程组,我得先试探性地调线程数,再观测实际吞吐量,接着改线程数,再跑一轮,反反复复。更麻烦的是,被测系统一旦在压测过程中变慢,真实到达率会自动下跌,之前压测得到的数据就很难作为容量结论的依据。

1.2 基于需求的压测和基于并发的压测,是两条路线

业内通常把负载模型分成两类:一类叫“基于并发的压力测试”,另一类叫“基于需求/到达率的压力测试”。前者更关注系统在特定并发下的响应表现,比如“300 个并发用户同时操作时,平均响应时间是多少”;后者更关注系统能否承载预设的业务流量,比如“每秒 1200 个请求持续半小时,资源占用和错误率是否达标”。

Arrivals Thread Group 走的就是第二条路线。它把“每秒到达量”作为压测工具的输入目标,而不是把“并行用户数”当作目标。这样做的好处是:无论被测系统变快还是变慢,压测端都会尽量维持预设的到达率。系统变慢时,插件会增加虚拟用户数来保证仍有这么多请求被发起;系统变快时,则会适当回收线程,避免实际流量远超预期。

我常用一个生活化类比来解释这件事:标准线程组像餐厅里固定数量的座位,不管翻台快慢,座上永远只有那么多人;Arrivals Thread Group 像餐厅门口按计划放进来的客流,每分钟想放进多少位顾客是可以提前定好的,服务员忙不过来时,门口排队的人也不会减少。对容量评估来说,“按门口客流控制”比“按店内座位控制”更贴近线上真实流量特征。

1.3 JMeter 自带方案够不够用

有人可能会问:标准 JMeter 不是有 Constant Throughput Timer(恒定吞吐量定时器)吗?它也可以控制吞吐量,为什么还要用插件?

Constant Throughput Timer 的设计思路是给每个线程下发“请求间隔”,让每个线程按一定频率发起请求,从而控制总吞吐量。它在轻量场景下确实能起到限速作用,但它并不理解“当前需要增加还是减少线程”。如果被测系统变慢,单个线程的循环间隔会受执行时间干扰,实际吞吐量依然很难稳定。Arrivals Thread Group 则把“根据实测到达率动态调整并发数”这个关键逻辑内置到了负载生成机制里,效果要更直接,也更符合真实业务流量的到达规律。

2. 插件原理拆解:Arrivals Thread Group 的闭环是怎么工作的

2.1 核心思想:测量实际到达率,再动态增删线程

从实现角度看,Arrivals Thread Group 并不是简单地创建一堆线程然后等它们跑完,它更像一个带反馈闭环的调度器。插件内部会不断统计已完成请求的数量,除以一个统计窗口时间,得到当前实际到达率。然后拿这个实际到达率跟我们设定的目标到达率做比较:如果低了,说明当前并发数不够,需要增加线程;如果高了,说明并发过多,需要减少线程。

这个逻辑看起来不复杂,但难点在于“增删线程”不能太激进,否则负载会上下抖动,反而给被测系统制造锯齿形的流量冲击。所以插件在做调整时会结合一个时间窗口和步长策略,尽量让到达率平滑地向目标值靠拢。

2.2 几个关键参数,别等用的时候才翻文档

在实际配置界面里,Arrivals Thread Group 涉及的参数比标准线程组多一些。我用项目里的典型配置来说明每个参数的含义:

参数含义我常用的配置
Target Rate目标到达率,即最终希望达到的每秒请求数根据业务预估填写,比如 1200
Ramp Up Time到达率从 0 增加到目标值所需的时间一般设 30-60 秒,避免瞬间冲击
Hold Target Rate Time到达率维持目标值的时间结合测试总时长设定,比如 600 秒
Iterations每个线程执行的循环次数,通常填 -1 或足够大持续压测时填 -1
Threads Initial Delay线程启动的初始延迟,脚本级参数多用于多脚本压力错峰

这里特别注意:Target Rate 的单位是“每秒到达的请求数”,不是“线程数”。如果脚本里有多个 Sampler,每个线程执行一次循环会产生多个请求,那么实际每秒到达的请求数通常是“目标到达率 × 单个线程循环内的请求数”。这是很多人配置完发现“实际吞吐量比 Target Rate 大好几倍”的原因。

2.3 跟 Constant Throughput Timer 相比,差别在控制手段上

我把这两种方案在同一个压测场景里对比过。Constant Throughput Timer 是通过调整线程内的 sleep 时长来限制速率,本质上是“按比例延时”;Arrivals Thread Group 是通过调整活动线程数来控制速率,本质上是“按需配置容量资源”。

这里有一个很实用的判断标准:如果你只是想控制某个 Sampler 的请求频率,避免给下游造成过大压力,用 Constant Throughput Timer 这类方案足够;如果你的目标是“模拟一个场景,让每秒到达系统的请求数无限接近某个指定值”,那就应该选择 Arrivals Thread Group。

另外在源码层面,Arrivals Thread Group 的目标控制还跟“平滑到达”有关。插件内部会把到达率拆成小时间片来调度,而不是一次性把一堆请求全部砸进去。这种机制对模拟秒杀、热点活动、消息推送突发这类流量形态特别有用。

3. 源码解析:Arrivals Thread Group 内部的关键机制

3.1 类结构概览:从 GUI 到运行时

从插件源码的组织方式来看,核心分为三个层次:可视化配置层、线程组逻辑层、运行时调度层。可视化配置层负责把用户在界面填写的数据转成线程组的属性;线程组逻辑层负责创建 JMeter 引擎认识的线程集合;运行时调度层负责真正控制线程的启动、停止和并发数量调整。

可以理解为:用户在界面上填的参数,最终会通过属性对象传给运行的线程组实例;线程组实例在启动之后,会进入一个循环控制逻辑,不断读取配置的目标到达率,并根据当前实测值调整“还让多少个线程继续跑”。

3.2 调度循环的简化还原

网上能看到不少源码层面的分析,但很多文章贴的代码片段太细,不容易抓住主干。我把调度逻辑简化为下面这段伪代码,思路跟正式源码基本一致,只是去掉了一些锁和边界处理的细节:

public class ArrivalsThreadGroup { private volatile double targetRate; // 目标到达率 private volatile double currentRate; // 当前实测到达率 private volatile int activeThreadCount; // 当前活动线程数 public void run() { long windowStart = System.currentTimeMillis(); long completedCount = 0; while (!shutdownRequested) { long now = System.currentTimeMillis(); long windowDur = now - windowStart; if (windowDur >= statsWindowMs) { currentRate = completedCount * 1000.0 / windowDur; completedCount = 0; windowStart = now; if (currentRate < targetRate * threshold) { int toAdd = estimateThreadsToAdd(targetRate, currentRate); adjustActiveThreads(toAdd); } else if (currentRate > targetRate) { int toRemove = estimateThreadsToRemove(targetRate, currentRate); adjustActiveThreads(-toRemove); } } if (newRequestCompleted()) { completedCount++; } sleep(scheduleIntervalMs); } } }

从这段简化逻辑里可以看到几个关键点:第一,插件并不是每发一个请求就做一次调整,而是以“统计窗口”为单位进行周期性判断,避免控制过于频繁;第二,实际线程数的增减用的是估算函数,不会一次把线程补到位,而是当偏差持续存在时逐步逼近;第三,整个调度循环有一个内部休眠间隔,控制线程启动和回收的节奏,这也是负载曲线能保持平滑的原因之一。

3.3 “动态增删线程”背后,其实就是轻量的容量控制器

在正式源码里,线程增删并不是直接调用线程对象的 start 或 stop,而是通过 JMeter 的线程组上下文接口来注册或释放虚拟用户。每次需要增加线程时,插件会创建一个新的虚拟用户实例,并把它加入运行队列;需要释放线程时,则会在当前线程执行完当前 Sampler 后,让该实例自然退出。

为什么不在线程执行一半的时候强行中断?我在做过压测工具开发后深有体会:强行中断线程会造成事务不完整,计数也不准确。Arrivals Thread Group 选择了“等当前采样完成后再退出”的策略,这样既能控制并发数,又不会破坏正在请求的业务事务。代价是线程收缩会有一点滞后,但换取的是数据和流量形态的真实性。

还有一点值得提:插件在扩容线程时会有一个“估算新增线程数”的过程。这个估算不是拍脑袋,而是基于当前实测到达率和目标到达率的比例,同时考虑统计窗口内单个线程平均贡献的请求数。用公式粗看就是:

新增线程数 ≈ (目标到达率 - 当前到达率) × 统计窗口时长 / 单线程在窗口内的平均请求数

虽然正式源码里的计算要更复杂一些,但理解这个思路,就明白为什么插件能根据系统快慢自动扩容了。

3.4 平滑控制和防抖措施,是源码里最值得学的部分

我在读源码时最关注的是“防抖”逻辑。理论上,如果系统响应时间突然变大,实测到达率会瞬间下跌,这时候调度器如果立刻大量补线程,就可能造成线程数猛增,然后系统响应进一步恶化,形成恶性循环。为了避免这种情况,源码里通常不会只根据一个窗口的数据就做大幅调整,而是会叠加一定的滞后判断,比如连续几个窗口都出现偏差,才触发扩容。

这个设计给我一个启示:控制负载生成,不能只看瞬时值,得容忍一定范围内的波动。我自己在配置 Target Rate 时,也不会把统计窗口设得太短,通常会结合聚合报告的时间粒度去理解。如果你用的版本把统计窗口暴露成配置项,建议设置成 3-5 秒,太短反而容易看到负载曲线抖动。

4. 在性能测试中怎么用:场景选择、参数配置和实操步骤

4.1 什么场景适合用 Arrivals Thread Group

先给一份我常用的选型对照表,方便你根据压测目的快速判断:

场景推荐模型理由
评估系统最大并发能力标准 Thread Group重点看并发用户下的响应时间
评估系统每秒业务请求容量Arrivals Thread Group重点看固定到达率下的容量表现
模拟线上用户逐步增加Stepping Thread Group / Ultimate Thread Group重点看渐进式加压过程
模拟突发流量和秒杀波峰Arrivals Thread Group + 平滑到达重点看到达速率的可控性

结论就是:如果你的测试目标是“系统能不能扛住每秒 1000 次下单请求”,用 Arrivals Thread Group;如果你的测试目标是“300 个用户同时在线时的体验”,用标准线程组更适合。

4.2 一个完整的配置案例

我之前测过一个模拟项目 X 的查询接口,目标比较明确:要确认系统是否能在“每秒 1200 个请求”的持续压力下正常运行 10 分钟。当时的配置大致如下:

  • Target Rate:1200
  • Ramp Up Time:60 秒
  • Hold Target Rate Time:600 秒
  • 线程数上限:我特意限制在 300 以内
  • 循环次数:-1
  • 并发用户取样策略:每个线程每次循环请求 1 次接口

测出来之后,我对比过聚合报告里的实际吞吐量,基本稳定在 1180-1210 之间,误差很小。相比之前用标准线程组时一到高峰期吞吐量就掉到七八百的情况,这个模型的稳定性明显更好。

操作步骤上,核心就三步:先在插件管理器中确认 Arrivals Thread Group 已安装;然后在测试计划里添加线程组,类型选择 Arrivals Thread Group;最后把目标到达率和持续时间填好。调试时先用 100-200 的小目标跑通脚本,再慢慢调到目标值。

4.3 别忽略“预热期”和“稳定期”的观察差异

用 Arrivals Thread Group 压测时,Ramp Up Time 不只是“慢慢加压”这么简单,它还影响到达率曲线的斜率。Ramp Up 设得太短,比如只有 5 秒,压力会在很短时间内冲到目标值,适合模拟突刺型流量,但对系统来说太暴力,容易得出偏离真实容量的结论;Ramp Up 设得太长,预热期会占用测试总时长,导致稳定期数据不够充分。

我的习惯是:除非专门测试瞬时冲击,否则 Ramp Up 至少占测试总时长的 10%-20%。比如总时长 10 分钟,预热期设 60 秒左右,剩下的时间才用于观察稳定容量和资源水位。

4.4 跟断言和监听器配合的具体建议

Arrivals Thread Group 控制的是“请求到达速度”,但测试结论还是得靠响应时间、错误率、服务端资源等多个维度综合判断。我在压测时通常会加这么几个指标收集点:

  • 响应时间分布:看 TP99、TP999 是否在接受范围内
  • 错误率:每秒到达率稳定时,错误率是否也稳定
  • 服务端 CPU、内存、连接数:判断瓶颈到底出现在哪个环节
  • 队列深度:如果下游有消息队列,看消费积压情况

这里有个容易忽略的细节:到达率模型维持了请求量,但系统一旦开始降速,响应时间上涨是必然现象。所以报告里不要只看“到达率达标”,要结合响应时间和错误率一起看,否则容易得出“系统性能不错”的错误结论。

5. 实战中容易踩的坑和排查思路

5.1 “目标到达率”不等于“每秒请求数”的糊涂账

这是最常见的问题。脚本里如果加了多个 Sampler,比如一个登录请求再加一个查询请求,Arrivals Thread Group 的 Target Rate 是按“完整线程迭代”来计算,还是按“单个 Sampler”来计算,取决于插件的统计口径和 JMeter 的事务粒度。

我遇到过一个场景:Target Rate 填 200,实际跑到 600 多,一看脚本里有 3 个 Sampler,等于 1 个线程迭代产出了 3 个请求。解决方法是:要么把多个 Sampler 包到一个事务控制器里,要么按“单个请求目标 = 总目标 / 每次迭代请求数”来反推。压测前先用 10 秒短跑验证实际吞吐量,再决定是否调整配置。

5.2 线程数上限和 JMeter 压测端资源的平衡

Arrivals Thread Group 自动扩容的前提是:压测机本身有足够的线程资源。如果你限制的线程数上限太低,比如只给 50,而目标到达率需要 200 并发才能打满,那么实际到达率就永远达不到目标值。反过来,线程数上限设得太高,JVM 内存和本地连接数可能成为新的瓶颈。

我通常的做法是:先用并发模型粗跑一轮,估计出打满目标到达率大概需要多少线程,然后给 Arrivals Thread Group 设置一个相对宽松但合理的上限,至少是估算值的 1.5 倍。同时在命令行模式下跑压测,并监控压测机自身的 CPU 和堆内存,避免压测端先崩。

5.3 线程回收慢,导致压测结束时流量没有立刻停

用 Arrivals Thread Group 跑长时压测时,结束后线程不会像标准线程组那样立刻全部停止,而是会等一批正在执行的请求完成后再退出。这个设计前面说过是为了事务完整性。但如果你要在压测结束后马上看 JVM 线程数,可能会发现还有一些残留线程在收尾,这个不是 bug,是正常的平滑退出过程。

如果残留时间过长,检查一下有没有事务控制器或同步定时器卡住了线程。尤其是用了同步定时器(Synchronizing Timer)的场景,如果线程一直在等待凑齐指定数量的请求,Arrivals 动态线程数又发生了变化,就可能出现线程迟迟无法完成循环的情况。

5.4 跟其他插件组合时的加载顺序问题

Arrivals Thread Group 属于自定义线程组,它跟 JMeter 的默认线程组在 UI 上的添加位置不一样。有些人把它放在 setUp Thread Group 或普通线程组后面,结果发现压测开始后实际生效的负载模型还是默认线程组的逻辑。

排查方法很简单:在测试计划树里确认线程组的类型,右键添加时不要选错层级;如果测试计划里混合了多种线程组,尽量让 Arrivals Thread Group 单独作为一个压测模块,避免多个线程组叠加后重复发起请求。

5.5 用命令行跑压测时,别漏掉插件依赖

本地 GUI 能跑通,换到命令行环境却提示找不到线程组,这种问题我自己遇到过。Arrivals Thread Group 依赖对应的插件 jar 包,如果服务器上的 JMeter 没安装插件管理器和对应扩展,启动命令执行时就会直接失败。解决方法是把整个 JMeter 目录连同 lib/ext 里的插件包一起同步到压测机,或者用插件管理器的命令行模式提前完成安装。

最后分享一个我在实际使用中形成的习惯:每次配置 Arrivals Thread Group,我都会先跑一个 30 秒的小流量脚本,观察实际到达率曲线和目标值之间有没有稳定偏差。等偏差在 5% 以内,再开始正式长时压测。这个小动作看起来多花了半分钟,但能帮你避开上面一半的坑。

这个插件的价值不在于它比标准线程组“高级”,而是它帮你把压测目标从“我要多少线程”转回业务本身的语言:“这个系统每秒需要承接多少请求”。如果你之前一直被线程数和吞吐量之间的换算困扰,不妨从一个小场景开始试试到达率模型。压测工具只是一个表象,真正重要的,是你能否用对模型来描述生产环境里真实发生的流量形态。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 8:15:24

Transformer核心原理:词嵌入、自注意力与PyTorch实现详解

如果你曾试图用循环神经网络&#xff08;RNN、LSTM&#xff09;处理长文本&#xff0c;一定被那种“读到后面忘了前面”的无力感折磨过。序列太长时&#xff0c;隐状态塞不下那么多信息&#xff0c;训练还容易梯度消失&#xff0c;更别提并行化几乎为零。Transformer就是冲着这…

作者头像 李华
网站建设 2026/10/20 8:15:00

大麦网抢票工具实战:Python接口直连与余票监控全解析

简介&#xff1a;这是一份针对大麦网在线购票场景的自动抢票工具源码&#xff0c;适合有一定Python基础、希望深入理解购票自动化原理的开发者。工具基于网络爬虫与浏览器自动化技术&#xff0c;能够模拟用户完成页面刷新、购票信息填写及订单提交&#xff0c;显著提升热门演出…

作者头像 李华
网站建设 2026/10/20 8:14:48

Firestore原生向量搜索实战:RAG场景下的存储选型与落地指南

上个月给内部知识库做RAG问答系统时&#xff0c;我看了一圈向量存储的选型&#xff0c;最后反而把目光停在了一个之前没怎么重视的选项上&#xff1a;Firestore。团队的业务数据早就在这个文档数据库里躺着&#xff0c;用户权限、文档归属、品类标签全都以字段形式存在集合里。…

作者头像 李华
网站建设 2026/10/20 8:14:11

AI文本鉴别技术全解析:从统计特征到神经网络分类器

1. AI文本鉴别到底在鉴别什么1.1 从一场“真假美猴王”的争论说起前阵子在一个技术交流群里&#xff0c;有人贴了一段两千字的产品文案&#xff0c;问大家“这是人写的还是模型生成的”。群里二十来号人&#xff0c;猜对的不到一半。有人从用词习惯判断&#xff0c;有人从段落结…

作者头像 李华
网站建设 2026/10/20 8:14:08

JOSE规范在PHP中的实践:JWT签名、JWE加密与JWK密钥管理

1. 先搞清楚&#xff1a;JOSE到底解决什么问题聊到JWT&#xff0c;大多数PHP开发者的第一反应就是firebase/php-jwt&#xff0c;三五行代码签发&#xff0c;十行代码验证&#xff0c;用起来确实爽。但是真的是所有场景都能这么顺吗&#xff1f;真不一定。当你遇到需要多算法切换…

作者头像 李华