ARTICLE DETAIL

资讯详情

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

JMeter分布式压测实战:Master-Slave架构、配置与排坑指南

JMeter分布式压测实战:Master-Slave架构、配置与排坑指南 1. 为什么单机压测会“卡脖子”分布式到底解决什么问题1.1 单机压测的瓶颈到底在哪做性能测试时间长了你会发现一个特别真实的现象脚本在本机跑得好好的线程数一加到上千JMeter进程自己先把宿主机的CPU吃满了网卡也打满了最后压出来的曲线跟心电图似的跳得没法看。这不是目标服务扛不住而是压测工具自己先趴下了。JMeter的分布式性能测试就是解决这个问题的标准姿势——把压力生成从一台机器拆到多台机器上用一组Worker节点去分担负载再用一个Controller节点统一下发脚本和收拢结果。为什么会这样JMeter本质是一个Java进程它发起请求时每个虚拟用户对应一个线程每个线程在内存里要维护自己的上下文、结果采样、聚合数据。线程数上去之后JVM堆内存、GC、线程上下文切换、socket文件描述符这些资源都会被快速吃掉。我实测过一个8核16G的压测机压一个很轻量的JSON接口单机跑到1000并发JMeter进程的CPU已经飙到85%以上GC停顿明显目标服务的响应时间波动反而比低并发时更大——这个数据根本没法用。很多人这时候以为目标服务到了瓶颈其实完全不是。压测机自己先到瓶颈了给目标服务制造的压力从“一个巨大的稳定流量”变成了“一阵一阵的毛刺流量”后面的分析全得推翻。要判断压测机是不是瓶颈最简单的方法就是一边压一边看压测机的资源占用CPU超过80%、内存占用持续攀升、网络打满那就说明压测端饱和了。还有一个更直观的方式用top看JMeter进程所在CPU是不是一直在user态非常高同时目标服务所在机器的CPU可能还不到50%这种对比反差基本就是压测机瓶颈的实锤。1.2 什么时候该上分布式什么时候不该上我见过不少团队把分布式当成压测的“银弹”脚本还没调好就直接开10台机器压结果问题出在参数化数据没分发、每台机器都在用同一个账号并发甚至因为结果汇总机制理解错了最后导出的数据对不上。分布式压测引入了新的复杂度和新的故障点它不是用来弥补脚本缺陷的。我的建议是出现下面几种情况再考虑分布式第一单机压测时压测机自身CPU或内存已经超过80%继续加并发会出现明显的抖动第二要模拟的并发用户量很大比如成千上万单台机器无论如何开不出这么多稳定线程第三需要从多个来源入口同时发起流量模拟真实世界的多节点访问。前两条是最常见的第三条属于高级场景。但如果你的接口只是压200并发、单机CPU只用了40%那根本没必要上分布式。先优化脚本、减少无用监听器、关掉不必要的断言调过一轮之后再判断。注意这里说的监听器优化是个大坑聚合报告、察看结果树这类GUI组件在命令行压测时应该去掉它们会占用大量内存和IO尤其结果树会在取样时做额外序列化。先做这些低成本的优化再考虑架构层面的扩容是我一直坚持的顺序。2. 分布式压测原理与架构Master-Slave到底怎么工作2.1 Master和Slave的分工分布式JMeter用的是典型的Master-Slave架构也叫Controller-Worker。Master就是那台你用命令行发起压测的机器Slave是被远程拉起的worker节点。Master不直接执行测试逻辑它负责三件事把jmx脚本分发给所有Slave、通知Slave开始执行、接收Slave回传的采样结果Slave才是真正干活的人每个Slave加载完整的测试计划副本在自己本机创建指定数量的线程组并发请求。这里有个大家容易理解的误区不是Master和Slave把线程拆开平分、然后各自负责一部分接口。实际上每个Slave都会完整地跑一遍脚本也就是说如果你在Master上配置了一个线程组里面有500个线程那么每一台Slave都会起500个线程。3台Slave加起来就是1500并发。这个数量认知非常重要设计测试方案的时候先定好“每台Slave的线程数”再乘以Slave数量才是真正的总并发量。Worker回传结果的方式也值得说一句。JMeter从4.0开始改进过RMI通信的配置方式每个Slave的采样结果会打包后异步回传给MasterMaster再统一聚合输出。所以在Master上看到的聚合报告、生成的HTML报告都是集群级的总览。如果中途某个Slave掉线了Master日志里会体现出来但已有的结果依然会保留不会被全部重置。2.2 RMI通信和两个关键的端口JMeter的分布式通信底层是Java RMI。别看它是个老协议实践里面出坑基本都出在“没搞清楚它到底要用哪些端口”上。一台Slave对外至少需要两个端口RMI注册端口默认是1099这是Master找到Slave的入口还有数据交换端口需要通过server.rmi.port来固定。在大规模压测场景下如果你没有固定server.rmi.portJava会随机挑一个可用端口来传输数据防火墙一开Master和Slave之间的数据回传就会失败表现就是Slave启动了、Master也连上了但压测开始后迟迟收不到结果。所以标准做法是在每台Slave的jmeter.properties里显式设置server.rmi.port10991 server.rmi.ssl.disabletrue然后把1099和10991都放行。注意这里的放行是双向的Master能访问Slave的两个端口Slave回应Master时Master本地的随机高位端口也需要能进出。最粗暴但有效的排查姿势是先在Slave上启动jmeter-server然后单独开一个JMeter客户端GUI在“运行-远程启动”里逐个试连能看到结果就说明网络通了。GUI模式下其实就是分布式压测最直观的验证方式虽然压测时不推荐开GUI但第一次联调用它非常合适。3. 环境准备与实操配置从零跑起一套分布式集群3.1 版本、JDK、hostname这些硬前提先说版本。Master和所有Slave的JMeter版本必须一致最好连JDK版本都保持一致我踩过最深的坑就是Master装的是JDK11、Slave还留在JDK8远程启动报各种莫名的序列化错误。建议直接统一用JMeter 5.6.3配JDK11官方下载包解压即用安装配置教程网上到处都是但版本一致这个原则很少被强调。JMeter没有动态兼容老版本Slave的机制老版本脚本文件虽然能打开远程协议却未必兼容。然后是hostname解析。很多人启动jmeter-server看到日志里写的是“server listening on /127.0.0.1:1099”就很开心以为起来了结果Master远程创建引擎时连不上。原因是Slave机器没有把自身hostname解析到内网IPRMI回绑时用了回环地址。解决办法很简单编辑/etc/hosts把当前机器的hostname映射到内网IP让JMeter知道该对外广播哪个地址。# /etc/hosts 192.168.1.11 jmeter-worker-01改完重启jmeter-server再留意日志里监听地址是否变成了192.168.1.11。这个细节排查起来特别费时间但处理成本极低属于必须提前做的基础动作。3.2 jmeter.properties里到底要改哪几项打开bin目录下的jmeter.properties我习惯只关注这几项其它的保持默认# 控制端指定所有worker地址用逗号分隔 remote_hosts192.168.1.10,192.168.1.11,192.168.1.12 # 数据交换固定端口 server.rmi.port10991 # 内网压测直接关闭RMI SSL证书不一致太折腾 server.rmi.ssl.disabletrue # 压测结束后强制退出JVM避免残留进程占端口 jmeterengine.force.system.exittrue # 结果传输模式 modeStandardremote_hosts这行如果只用命令行-R参数可以不写二者选一我的习惯是都写上因为某些JMeter版本在UI远程启动时只认配置项。关于mode这里解释清楚Standard代表每个采样结果生成后立即回传实时性最好但Master的IO压力大StrippedBatch是异步攒一批再回传能明显降低网络和Master的负载但结果统计会有滞后。短时间高并发压测用Standard长时间稳定压测用StrippedBatch别反过来用。所有Slave机器都要把jmeter.properties改好尤其server.rmi.port和ssl.disable。控制端单独改remote_hosts就够了。改完之后在每台Slave的bin目录执行# 前台启动便于看日志 ./jmeter-server如果有很多台Slave用nohup后台启动nohup ./jmeter-server /tmp/jmeter-server.log 21 启动成功的标志是日志出现类似“Creating rmiregistry server bound to port 1099”和“Server started ...”。4. 脚本改造、参数调优与一次完整实操4.1 分布式下必须改的脚本细节CSV、断言、依赖jar脚本是另一个大坑。你辛辛苦苦录好的JMX脚本在单机跑得好好的丢到分布式环境里经常出现“文件找不到”或者“每台跑的数不一样”。先说CSV参数化。CSV Data Set Config默认读取的是相对路径而不同节点的JMeter工作目录可能不一样。最稳妥的方式是把CSV文件放到每台Slave的JMeter bin目录下脚本里填写相对路径或者拷贝到一致的绝对路径。压测前逐台检查文件是否存在别只检查Master自己的。还有Shared Mode分布式环境下这个选项表示的是“在同一台Slave的同一进程内共享”不是跨Slave共享所以不同Slave会各自从CSV头部按顺序读取。要避免一个账号被多台Slave同时使用可以把每台Slave的CSV文件做错位处理或者给CSV加上动态参数拼接。再说道监听器和断言尤其经常被搜到的beanshell断言。Beanshell是JMeter里默认内置的脚本语言但它是解释执行的性能非常差。我之前压了一个带复杂校验的接口用Beanshell断言在每轮采样里做字符串匹配单机压测CPU多花了30%分布式的成本会按机器数翻倍放大。正确的做法是用JSR223取样器或JSR223断言语言选Groovy预编译执行性能好一个数量级。Groovy脚本里能用到的类注意要把依赖jar放到所有Slave的lib目录下否则你在Master本地写的引用到了Slave上就是NoClassDefFoundError。还有一个特别隐蔽的坑取样器里依赖的外部文件比如jks证书、上传的文件附件。上传文件接口的路径每台Slave都要存在否则部分节点会直接报错但其它节点还在正常压测最终数据混杂在一起非常难排查。我的做法是在压测前的检查清单里明确列出一项“所有外部依赖文件已同步到所有Slave”宁可多花几分钟也不要跑完发现数据没法用。4.2 一次完整的分布式压测实操记录下面用一个实际案例串一遍完整流程。假设目标接口是一个简单的GET接口需要模拟1500并发准备3台2核4G的Slave和1台控制机。第一步先确认脚本在单机环境下能用。本机100并发跑一遍看接口响应正常、吞吐量数据稳定再进行后续步骤。这一步能过滤掉90%的脚本问题。第二步配置环境。三台Slave按上面的方法改好jmeter.properties启动jmeter-server。控制机上写好remote_hosts。验证连通性可以这样登录控制机执行jmeter -n -t test.jmx -R 192.168.1.10,192.168.1.11,192.168.1.12 \ -l result.jtl -e -o report第一次跑建议线程组里每个Slave设置500线程循环次数设个1次或2次先看能不能跑通、结果能不能正常回传。第三步跑完整压测。真实场景下线程组一般设置循环次数为持续时间比如300秒方便观察曲线。压测启动命令加上-g参数可以在结束后直接用GUI打开聚合报告jmeter -n -t test.jmx -R 192.168.1.10,192.168.1.11,192.168.1.12 \ -l result.jtl -e -o report -g result.jtl压测完成后直接用浏览器打开report目录下的index.html即可。JMeter 5.6.3生成的HTML报告里需要重点看三个地方第一个是Summary里的吞吐量第二个是响应时间的90%、95%、99%分位线第三个是按线程组分开的详情页里的错误率。如果你需要在linux压测过程中直接查看压测接口的响应内容我建议给脚本加一个简单的Sample Result Save Config或者“察看结果树”并勾选保存响应字段然后压测结束后在JTL文件里搜索关键字。这里有一个经验不要在持续压测时开着“察看结果树”来实时看响应它会把你压测机的内存直接拖垮。正确的方式是把响应保存到文件压测完再查不影响压测过程。4.3 聚合报告和命令行报告怎么读才准最后说结论解读。聚合报告里的Samples是集群所有采样点的总和Errors是所有Slave的失败总数Throughput是Master端统计的集群总吞吐。注意如果你的压测脚本里定义了多个请求聚合报告会把每个请求单独列出来也会有一个ALL汇总。常用指标我放了一张速查表指标含义参考用法Samples总请求数与实际压测时长、线程数对照检验数据是否完整Errors失败请求数压测中进行阶段观察失败率高时优先查目标服务Average平均响应时间结合分位数一起看单看平均容易掩盖长尾90%/95%/99% Line响应时间分位值长尾场景重点看99%Throughput单位时间完成的请求数与目标压测流量对应判断是否达到预期Received/Sent接收/发送字节数怀疑压测机带宽瓶颈时必看这里还有一个很多人都没注意的点聚合报告是间隔取样汇总的它的统计间隔默认比较粗所以用命令行执行压测后最好再单独生成HTML报告里面的时序图、分位数都比聚合报告可靠得多。我早期做分布式压测直接用GUI聚合报告里的吞吐量当最终结果跟后来用命令行报告一对比差了10%以上就是因为取样间隔导致的统计差异。数据采集方式不同结论就可能不同这比压测本身更值得警惕。5. 常见问题与排查技巧实录5.1 启动与连接类问题分布式压测的问题我按照出现频率整理了一份排查清单。第一类是远程启动失败。日志里出现“Remote-Disconnect, ensure to remove the property”或者“Failed to create the engine”大概率是版本不一致、hostname解析错误、防火墙没放行1099。按前面说的三步走统一版本、改hosts、放行端口。如果还不行加上-server的日志参数跑一次看Slave端的具体报错基本都能定位。第二类是RMI的SSL握手失败。报错里能看到“SSL”关键字。内网压测直接把server.rmi.ssl.disabletrue加进去就行不用重复生成证书。第三类是端口被占用。特别是频繁重启压测机之后java进程没退干净1099或10991被占启动jmeter-server时日志会明确报“Address already in use”。用jps或netstat找出残留进程kill掉再重启。这正是上面配置jmeterengine.force.system.exittrue的原因压测结束自动退出省得手动清。5.2 压测过程中的数据类问题真正解决起来最费时间的是压测运行中的问题。这里我把搜索里那个典型的报错单独挑出来讲org.apache.http.conn.httphostconnectexception: connect to。出现这个报错意思是JMeter在向目标接口建立TCP连接时失败了。有两种情况一种是目标服务本身连接数打满连接池耗尽点开错误时间窗口去看服务端的监控指标另一种是压测机侧在大量新建连接时触发了系统的文件描述符上限导致部分socket创建失败。排查时先在Slave上看看当前连接状态netstat -an | grep 目标IP | grep SYN_RECV | wc -l如果SYN_RECV数量巨大说明服务端backlog队列已满客户端请求排不上队这时优先看服务端配置如果Slave本地报“too many open files”那就是ulimit -n不够调大文件描述符上限ulimit -n 65535结果数据对不上也是高频问题。压测完发现总请求数比预期的“Samples 线程数 x 循环次数 x Slave数”少大概率是某个Slave中途掉线回传数据不完整。排查思路是去每台Slave的日志里看有没有OOM、GC或socket异常如果有Slave挂了Master的jtl里对应的那部分数据就是缺失的。所以分布式压测做完后把每台Slave的本地结果都保留一下别只依赖Master的汇总合并出现偏差时还能对比。5.3 压测机侧的资源调优建议分布式压测虽然拆了压力但每台Slave本身还有优化空间。记住几个关键点文件描述符要放开ulimit -n至少65535JVM堆内存不要无脑调大容易触发Full GC建议两台4G内存的Slave就配-Xms1g -Xmx2g8G内存配3G左右压测机上如果跑着太多无关服务先停掉尤其是那种自动更新的守护进程会让压测曲线莫名抖动。还有一个很重要的事第一次压测时在每台Slave上单独跑一遍脚本把线程数压到目标值看单个Slave的CPU是否接近100%。如果单台Slave就CPU 100%了说明脚本里的监听器、断言、日志输出消耗太大先回4.1节做脚本优化。反之如果单台Slave CPU才20%却还要继续加机器那就不是在解决瓶颈只是把数据变得更复杂而已。系统内核相关的网络参数调整我的建议是压测专用的Slave再改共用机器别动内核。核心就几个net.ipv4.tcp_tw_reuse1解决TIME_WAIT复用问题net.core.somaxconn适当调大解决连接排队net.ipv4.ip_local_port_range建议扩大段。改完sysctl -p生效不要在每个压测周期反复改改了之后前后数据就不具可比性了。最后再分享一个小技巧我在每次分布式压测前都会在脚本的线程组里加一个极短循环的验证任务只用1个线程跑2次用来检查集群连接、数据文件、断言是否都正常。跑通了再把循环次数调回真实压测值。这个习惯帮我省了大量返工时间比任何一种排查技巧都管用。毕竟分布式压测一分钟的机器成本不低跑一通无效数据后面所有分析都要从头再来。
返回列表