ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JMeter分布式压测环境搭建:从单机瓶颈到多机协作实战

JMeter分布式压测环境搭建:从单机瓶颈到多机协作实战 做压测这行越久越会发现一个事实单机 JMeter 的瓶颈往往比被测系统的瓶颈来得更早。明明脚本没问题、场景也没问题可压到一定并发量之后客户端自己先卡死了CPU 打满、内存飙升、网络连接数耗尽甚至 JMeter 界面直接失去响应。这种时候分布式压测就不再是“进阶选项”而是必须走的路。这篇内容我不会跟你讲太多虚的架构理念而是把我自己实际搭建 JMeter 分布式压测环境时踩过的坑、验证过的配置、调整过的参数一次性摊开来讲。目标只有一个照着做你能在半天内把一套可用的分布式压测环境跑起来并且能理解每一步背后到底在解决什么问题。1. 什么时候必须上分布式压测先别急着搭环境很多人一听“分布式”三个字就觉得很高级恨不得马上搞五六台机器试试。但我的建议是先冷静 30 秒想清楚你到底是“需要分布式压测”还是“只是觉得应该有分布式压测”。1.1 单机压测的瓶颈到底在哪儿单机压测的瓶颈通常出现在四个地方第一CPU。JMeter 本身是 Java 写的每一个线程组里的线程在执行逻辑时JVM 会分配对应资源。当并发线程数达到几千甚至上万时线程切换本身就吃掉了大量 CPU 周期。你去看 JMeter 所在机器的监控CPU 可能早就跑到了 90% 以上但真正发出去的请求量却远低于你的预期。第二内存。JVM 堆内存默认值比较保守虽然你可以在 jmeter.sh 里调大 HEAP 参数但单机内存终究是有限的。更关键的是JMeter 要在内存中保存每个请求的响应数据如果你开了响应断言或监听器几万条采样结果堆在内存里GC 压力会非常大表现为长时间的 Full GC压测曲线直接出现断崖。第三文件句柄。Linux 系统默认的 open files 限制是 1024虽然可以通过 ulimit 调大但即便调到 65535单机能够维持的 TCP 连接数也会成为限制。你要模拟大量长连接场景时这个瓶颈尤其明显。第四网络带宽。如果被测系统在公司机房而你压测机也在同一机房单机千兆网卡全速跑理论上也就能打到 900Mbps 左右。如果你模拟的是大 Payload 的接口比如图片上传、文件上传单机很容易就把带宽跑满压测结果自然失真。1.2 分布式能解决的和解决不了的分布式压测能解决的核心问题是把单机的 CPU、内存、文件句柄、网络带宽这些资源限制通过多机分摊来突破。理论上你有 10 台 Agent就能把单机的压测能力放大接近 10 倍。但分布式不是万能良药它解决不了脚本本身写得太烂的问题。如果每个线程循环里都做了大量的正则提取、JSON 解析、同步定时器等待加再多的 Agent也会因脚本逻辑太重而拖垮压测效率。它解决不了被测系统的网络链路瓶颈。Agent 机分布在多个网段经过交换机、防火墙、负载均衡之后网络延时本身就会影响压测结果。它解决不了“并发度”的准确性偏差。分布式环境下每台 Agent 的启动时间有先后严格意义上无法做到全局绝对同时只能通过同步定时器beanshell 或 Sync Timer尽量对齐。所以我的判断标准很简单单机压测时客户端机器的资源使用率超过了 70%且压测数据明显受客户端资源影响这时候再考虑上分布式。如果压测不到 200 并发就已经能跑满被测系统那你先解决的是被测系统的性能问题而不是压测工具的能力问题。2. 分布式压测架构与核心概念JMeter 分布式压测的逻辑其实很朴素一台机器当“指挥官”Master下发脚本和启停指令其他机器当“工人”Agent真正发起请求。Worker 跑完之后把采样结果回传给 MasterMaster 统一汇总展示。2.1 控制器Master与执行器Agent角色划分在 JMeter 官方文档里Master 叫 ControllerAgent 叫 Remote Engine也有人叫 Slave 或 Worker。角色划分非常明确Master只负责脚本分发、任务调度、结果收集、监控展示。它本身不发起压测请求所以对它的性能要求相对低但结果汇总和 UI 渲染还是会消耗一定的 CPU 和内存。Agent真正发起请求的机器。它从 Master 接收测试计划Test Plan在本地 JVM 中创建线程池按照脚本配置向被测系统发起请求。Agent 机器上不需要 JMeter GUI 界面只需要有 JMeter 的解压目录并运行 jmeter-server 启动脚本即可。有几台 Agent理论上就相当于有多少台“压测机器”。Master 通过 RMIRemote Method Invocation协议跟 Agent 通信端口默认是 1099这个端口后面配置改造时非常关键。2.2 通信原理与端口规划JMeter 分布式通信走的是 Java RMI。RMI 这个机制说穿了也不复杂Master 端持有 Agent 端的远程对象引用调用方法时通过序列化传递参数测试计划Agent 执行完再通过序列化回传结果。这里有两个端口必须搞清楚RMI 注册端口1099Agent 启动后会向本机的 RMI Registry 注册自己的远程对象Master 需要访问这个端口来发现 Agent 的引用。RMI 数据端口随机端口Agent 和 Master 建立连接后实际的采样结果数据传输会使用另一个端口。这个端口默认是随机分配的如果你有防火墙管控就必须改成固定端口否则防火墙会拦掉 RMI 回传的连接。默认情况下同一台机器上启动多个 Agent 也不是不行但每个 Agent 的 RMI 端口需要手动改而且多 Agent 共用同一台机器的网络带宽和 CPU效果打折扣。我一般不推荐在一台机器上堆多个 Agent除非你只是做界面展示实验。注意如果你在公司办公网或者云上做分布式压测不要盲目复制别人博客里的配置先看看你的网络安全策略。JMeter 的 RMI 通信本身并不加密敏感环境里需要在受信内网中运行或者做网络隔离别把压测集群暴露到公网。3. 环境搭建的完整实操过程很多人觉得 JMeter 安装很简单解压就能用。确实如此——单机版是这样。但分布式环境里你至少要做三件事环境准备、配置修改、启动验证。任何一个环节漏了都会出现“脚本能打开、远程就是连不上”的尴尬。3.1 环境准备与 JMeter 版本选择JMeter 是跨平台 Java 应用所以所有参与压测的机器包括 Master 和 Agent都需要先装好 JDK。版本上我建议统一用 JDK 8 或者 JDK 11JMeter 5.x 系列对这两个版本支持最稳。这里有一个容易被忽略的点所有机器上的 JMeter 主版本号必须一致最好连小版本号都保持一致。我之前有一次把 Master 装成 5.6.3Agent 装成 5.4.1结果测试计划分发过去之后Agent 端报了一堆 ClassNotFoundException严重浪费时间。原因是序列化机制对版本差异非常敏感RMI 序列化对象时使用相同的类标识小版本不一致也可能导致兼容问题。我的环境版本组合角色操作系统JDK 版本JMeter 版本MasterCentOS 7.9 / Windows 10JDK 8u202Apache JMeter 5.6.3Agent 1CentOS 7.9JDK 8u202Apache JMeter 5.6.3Agent 2CentOS 7.9JDK 8u202Apache JMeter 5.6.3操作系统方面Agent 优先选用 Linux原因很简单Linux 下的线程调度效率、文件句柄上限、网络栈表现普遍优于 Windows而且 Linux 机器做压测时不容易被 GUI 界面拖累。下载路径就直接去 Apache 官网不要从乱七八糟的第三方站点下载尤其不要用什么“绿色版”“破解版”这东西本身就是开源免费的完全没必要冒风险。下载后用命令行检查一下 SHA512 校验值防止在非官方渠道下载到被篡改过的压缩包。3.2 修改配置文件实现远程分发安装完成后关键就是改 Agent 端的jmeter.properties这个文件在 JMeter 解压目录的bin文件夹下。用文本编辑器打开找到以下几项# 远程主机列表多个用逗号分隔 remote_hosts127.0.0.1:1099 # 关闭 SSL提升兼容性内网环境可用 server.rmi.ssl.disablefalse我的实际配置内容大致如下remote_hosts192.168.1.101:1099,192.168.1.102:1099 server.rmi.ssl.disabletrue默认情况下JMeter 5.x 的所有版本都默认开启 RMI SSL这意味着 Agent 端和 Master 端需要交换密钥。如果没做配置启动时可能会报 SSL 相关错误。内网环境做压测时我建议直接关闭 SSL减少一层麻烦server.rmi.ssl.disabletrue但注意关闭 SSL 意味着 RMI 数据明文传输这个需要自己做技术和管理层面的权衡。如果你对安全性有硬性要求那就保留 SSL 并正确配置密钥但这部分工作量会大不少。Master 端也需要做三处调整第一处是改运行参数。编辑jmeter脚本或jmeter.bat文件里的HEAP参数把 JVM 堆内存调大。默认值一般是-Xms1g -Xmx1g这个对 Master 来说偏小了。我一般改成HEAP-Xms2g -Xmx4gAgent 端的堆内存则要根据 Agent 上要跑的线程数来定。经验值大约是每个虚拟用户线程预留 512KB 到 1MB 堆空间。如果单台 Agent 要跑 2000 并发堆内存至少给到 2GB 到 3GB 才算稳妥。第二处是修改jmeter.properties里的mode参数。默认是 Standard 模式Master 会收到所有详细采样数据并绘制图表。分布式场景下如果 Agent 数量多、QPS 高Standard 模式容易造成 Master 端内存暴涨、网络拥塞。我的建议是改成modeStripped只有统计信息回传省带宽、省内存modeStripped第三处是检查默认的 RMI 数据端口配置。JMeter 5.x 和 4.x 之间的配置差异就在这里——你需要确保 jmeter-server 在启动时读取的是同一个配置文件。3.3 启动与验证配置改完之后Agent 端进入bin目录执行以下命令启动服务./jmeter-server看到类似这样的日志说明 Agent 启动成功Created remote object: UnicastServerRef2 [liveRef: [endpoint:[192.168.1.101:1299](local),objID:[...]]]注意看日志里出现的端口那个是 Agent 的 RMI 数据端口默认情况下是从 1099 之后动态分配的。Master 端启动 GUI 界面打开jmeter./jmeter然后在 GUI 的菜单栏找到“运行” - “远程启动”如果配置正确会看到你配置的两个 Agent IP 出现在子菜单中。点击其中一个就能看到 Agent 开始执行压测任务并且在 Master 上实时显示聚合数据。如果远程启动菜单里面是空的或者点击之后没有反应恭喜你进入了最经典的问题排查环节这部分我放到最后面专门讲。提示如果是第一次验证分布式环境建议先用一个最简单的测试计划跑 10 秒钟看能不能正常回传结果。不要上来就压复杂业务脚本否则出了问题都不知道该往哪个方向排查。4. 分布式压测脚本的编排技巧很多人以为分布式压测就是把原来的单机脚本丢到远程主机上跑一下就完事了。实际上脚本编排没做好分布式不仅不能提升效果还会把测试结果搞成一团浆糊。4.1 数据隔离不要在变量上踩坑单机压测时你可以在一个 CSV 文件里放 1000 条测试账号线程组直接读取。分布式场景下就没这么简单了——所有 Agent 拿到的是同一个测试计划同一个 CSV 文件路径。如果你在测试计划里填写的是相对路径每个 Agent 会根据自己本地的相对路径去找文件结果可能就是有的 Agent 找到了、有的没找到。我常用的方案有三种共享存储把测试数据放在 NFS、MinIO 或者统一的文件服务器上所有 Agent 挂载同一个目录。这种方式适合数据量大、Agent 数量多的场景但部署复杂度高一点。本地复制在每台 Agent 的相同路径下放一份完全相同的 CSV 文件测试计划里使用相同的绝对路径。这种方式最简单适合数据量不大、变更频率低的场景。使用随机函数生成如果你压测的接口对数据格式没那么敏感完全可以在参数值里用${__Random(1,999999)}或者${__time(yyyyMMddHHmmss)}生成随机数据从源头避免数据分发问题。还有数据隔离的第二个维度每台 Agent 会不会用了相同的数据导致服务端出现冲突比如你用手机号做唯一约束数据量不够时每台 Agent 都在用同一批手机号反复注册最终的结果是服务端报了很多唯一性错误压测流量根本就没打到核心逻辑上。解决思路是给每台 Agent 分配不同的数据段。比如你有 4 台 Agent、每台要跑 5000 个手机号那就按区间切分Agent 1 用 13800000001 到 13800005000Agent 2 用 13800005001 到 13800010000以此类推。用user.properties里的自定义属性来判断当前 Agent 是几号然后用${__P(agentId,0)}读取属性在 CSV 文件名或脚本逻辑上做区分。4.2 结果合并与报告生成分布式压测的结果回传是个很容易让人误解的地方。可能你会以为 Master 收到的是每台 Agent 的“聚合结果”但实际上 Master 收到的是每台 Agent 的“采样结果流”它会在内存里做合并。这意味着两件事第一如果你压测结束后直接在 GUI 里查看聚合报告那个数据是全量合并后的没问题。但前提是 Master 端不能开太久如果压测跑了几个小时Master 内存会有很大压力。第二如果你用命令行模式跑并生成 HTML 报告输出路径要加上时间戳避免多轮压测报告相互覆盖。我习惯用一个 shell 脚本启动压测和生成报告#!/bin/bash timestamp$(date %Y%m%d_%H%M%S) ./jmeter -n -t /path/to/testplan.jmx -l /result/result_$timestamp.jtl -e -o /report/report_$timestamp -R 192.168.1.101,192.168.1.102注意这里的关键参数是-R它的作用远程启动指定 Agent。如果没有这个参数命令行模式默认只会跑本地脚本不会通知远程 Agent 执行。另外在线程组里设置“每台 Agent 的用户数”时要注意分布式压测的总并发数等于每台 Agent 的并发数 × Agent 数量。比如每个线程组配置了 500 并发你有 4 台 Agent总并发就是 2000。很多人容易在这个地方算错导致实际压测和预期目标差了好几倍。5. 真正上生产之前要解决的问题环境能跑通、脚本能执行才只是第一步。分布式压测真正让人头疼的地方是进入生产环境或者准生产环境之后暴露出来的各种边界问题。5.1 网络与防火墙配置最经典的问题场景你在测试环境跑得好好的换到生产网段或者云上环境突然 Agent 全部失联。原因大概率出在 RMI 数据端口的随机分配上。生产环境通常会配置防火墙策略只允许特定端口通信。而 JMeter 的 RMI 数据端口默认是随机的这就导致 Agent 回传数据时连接被防火墙拦截。解决方案是把 RMI 数据端口固定下来。在jmeter.properties中配置server.rmi.localport4000这个参数表示 Agent 回传数据的端口固定为 4000。然后在防火墙策略里放行Master 访问 Agent 的 1099 端口RMI 注册Master 访问 Agent 的 4000 端口RMI 数据回传注意server.rmi.localport这个属性只在 Agent 端配置生效。Master 端只需要保证能访问这两个端口即可。还有一点很多人不知道如果你用的是-R远程启动Master 和 Agent 之间的连接是双向的。Agent 启动时向 Master 注册Master 要能访问 Agent 的 RMI 注册端口但 Agent 回传数据时连接方向可能是 Agent 发起也可能是 Master 发起取决于 JMeter 的实现版本。稳妥起见安全组或防火墙里双向放行这两个端口。5.2 负载均衡与 Agent 分配策略有 4 台 Agent 不代表 4 台 Agent 都能跑满。实际压测中经常会发现某几台 Agent 的 CPU 已经 100%另外几台还有大量闲置。原因通常是脚本执行逻辑里用了同步定时器Synchronizing Timer或者用了全局的临界区Critical Section Controller之类的组件。这些组件在分布式环境下会把所有 Agent 的线程拉到一个共享锁上排队结果就是压测能力大打折扣。如果你确实需要精确对齐各台 Agent 的启动节奏我的建议是线程组里设置合理的Ramp-Up Period让每台 Agent 的启动并行度相对可控。避免全局级别的同步定时器。如果必须用那就接受总吞吐量下降的事实先在小规模验证下看你是否真的需要全局同步。每台 Agent 分配的业务模块尽量一致不要在一台 Agent 上同时跑登录压测和下单压测避免脚本复杂度不同导致负载分配不均。另外一个实操细节Agent 和被测系统之间的网络跳数要尽量少。最好 Agent 机放在跟被测系统同一机房或同一可用区内否则每一次压测的网络延迟就会成为干扰因素你很难判断性能波动到底是程序本身的问题还是网络抖动的问题。6. 常见问题排查与实操心得这部分是我最想写的。分布式压测环境从搭建到真正稳定运行我自己至少踩过二十几个坑下面挑出最典型、最容易遇到的一些问题整理成表格方便你直接对照排查。6.1 典型故障速查表故障现象可能原因排查方法远程启动菜单为空jmeter.properties中remote_hosts配置错误或者 Agent 未启动在 Master 上用telnet Agent IP 1099测试连通性远程启动后立刻失败RMI 数据端口被防火墙拦截在 Agent 端设置server.rmi.localport4000放行端口Agent 启动报 JVM 内存溢出堆内存配置过小修改jmeter脚本中的HEAP参数Master 收集结果后内存暴涨采样数据量过大Standard 模式回传全量数据修改modeStripped只回传统计信息Agent 找不到 CSV 文件脚本中使用了相对路径改为绝对路径或在各 Agent 相同路径下存放数据文件采样数据错乱、数据重复没有做数据分片各 Agent 使用相同测试数据按 Agent ID 做数据分片RMI 连接超时网络抖动RMI 默认超时时间太短修改 JMeter 系统属性中的 RMI timeout我最想特别强调的两个坑第一个坑是关于 RMI 端口没固定的问题。有一次我在云上环境搭集群Agent 启动时日志明明显示注册成功Master 也看得到 Agent但一启动压测采样数据就是不回传。排查了整整一个下午最后发现是云安全组只放行了 1099 端口RMI 数据端口是动态分配的根本没有放行。这个问题如果没经验真的是很难想到。第二个坑是关于jmeter-server启动时的工作目录。用./jmeter-server启动没问题但如果你用 systemd 或者其他服务管理器来托管进程必须确保工作目录是bin目录。JMeter 内部有一些相对路径的配置文件读取逻辑工作目录不对启动过程可能没有报错但远程执行就是异常。6.2 踩坑记录与优化建议根据我个人的实际操作经验还有几条锦上添花的建议建议一为每台 Agent 写一个启动脚本并加入监控。分布式压测长跑的时候Agent 进程有可能因为内存压力被系统 OOM Killer 干掉或者因为网络断连而挂掉。你不可能一直盯着日志看。我一般是这么处理的用 nohup 后台启动并写日志再配一个定时检查脚本发现进程不在就直接拉起。建议二压测结束之后一定检查各 Agent 的样本数量是否均衡。如果发现某台 Agent 的样本数明显少于其他几台说明这台机器可能存在资源短缺或者网络阻塞需要在下一轮压测前优先处理。建议三不要把 Master 放在一台性能太差的机器上。虽然 Master 不实际发起请求但它要接收所有 Agent 的大量采样数据还要跑 GUI 渲染图表。如果 Master 本身内存只有 2GB最直观的表现就是界面假死、结果更新延迟严重。建议四如果 Agent 数量超过 8 台建议拆分为多个 Master。一个 Master 最多管理多少 Agent 没有硬性限制但实践中到十几台之后Master 的 CPU 和网络都会成为瓶颈反而连累压测结果。拆成两三个小集群每个集群用单独的 Master汇总时再统一导出。写在最后的个人体会如果你初次上手我的建议是先别追求 Agent 数量先追求流程稳定。两台 Agent 能把 1500 并发稳定跑完远好过五台 Agent 一跑就崩、从头到尾都在排查问题。我可以很坦率地讲分布式压测环境的构建技术难点其实不在“分布式”这三个字上而在网络规划、资源分配、防火墙策略、数据分片这些看似琐碎的细节里。我曾经为了排查一个 RMI 数据端口问题折腾了好几天最后发现问题特别简单。这种经历大概每位做压测的人都会碰到几次。另外一个很重要的事在开始分布式压测之前一定要先想清楚一个问题——你到底测什么、用什么指标来判定通过/失败。分布式压测只是手段吞吐量达标、响应时间达标才是目的。如果这部分没有提前想好即使环境搭得再完美你也只是在制造一堆好看但不解决问题的数据罢了。
返回列表