资讯动态

JMeter压测MQTT全攻略:从连接模拟到性能分析

发布时间:2026/10/20 8:09:03 来源:尧图企业网站定制
去年年中做某物联网平台的设备接入压测我要在短时间内摸清自建的MQTT Broker到底能扛多少设备在线、每秒能转多少条消息。试了几个商业工具不是要授权就是脚本玩法太封闭最后还是落到JMeter上——加一个MQTT插件发布订阅、连接数、QoS、消息吞吐全都能压。整个过程踩了不少坑从插件装不上到订阅收不到消息、从clientId重复掉线到监听器把压测机内存吃满基本都趟了一遍。这篇就把我从零搭JMeter MQTT压测环境的全过程、脚本设计、参数选择、结果分析和排查经验一次性整理出来。不管你是刚接触性能测试的新人还是被MQTT压测折磨过的老手按着这篇走一遍至少能把脚本跑起来、数据看明白、坑提前避开。1. 为什么要用JMeter压MQTT而不是现成的专用工具1.1 MQTT压测到底在压什么MQTT和HTTP完全是两种玩法。HTTP压测是“你发一个请求服务器回一个响应”每个请求独立、无状态压的是接口吞吐而MQTT是长连接上的发布订阅模型客户端先和Broker建立TCP长连接然后在这个连接上双向推送消息。所以MQTT压测关心的是几个完全不同的维度连接能力Broker能同时维持多少万个TCP长连接这是设备接入场景最核心的指标。消息吞吐单位时间内Broker能转发多少条消息对应Pub/Sub的QoS处理能力。端到端延迟从发布者发出一条消息到订阅者实际收到中间隔了多少毫秒。稳定性消息量达到一定程度后连接是否会被踢、消息是否丢失或重复、延迟是否出现抖动。这四个维度里只有把连接、发布、订阅都模拟出来才能测到真实结果。这也是为什么不能拿一个普通HTTP压测脚本去套MQTT的原因——你就算用Socket手搓一个TCP发包工具也实现不了QoS握手、遗嘱消息、会话恢复这些MQTT协议层的细节。1.2 JMeter凭什么能承担这个活儿JMeter本身是免费的、社区庞大、插件机制成熟。虽然它原生不支持MQTT协议但社区早已提供了对应的采样器插件装完就能在测试计划里直接添加MQTT Connect、Publish、Subscribe三种采样器。对比专用压测工具JMeter的好处在于学习曲线低团队里做接口压测的人基本都会JMeter不用额外学一套专用脚本语法。场景组合灵活一个测试计划里可以同时安排“连接线程组”和“发布线程组”通过定时器、控制器、断言控制节奏比专用工具更贴合按业务场景建模的需求。结果生态成熟聚合报告、后端监听器、分布式压测都有现成方案适配时序数据库做实时可视化也很方便。零授权成本对于预算有限的团队用JMeter 开源Broker就能完成大部分MQTT压测需求。当然JMeter在高连接数场景下也有局限性比如单台压测机受文件描述符、内存、端口范围限制压到几万连接时本身可能成为瓶颈。但一般几十个线程到几千个线程的中小规模压测JMeter完全可以胜任。我这里讲的所有操作也是按这个规模来设计的。2. 环境准备JMeter、MQTT插件、Broker一条龙2.1 先装对JMeter和JDK版本JMeter是Java应用需要先有JDK。我用的是JMeter 5.x系列搭配JDK 8或JDK 11都行如果你用JMeter 5.5以上建议直接上JDK 11或17避免某些新特性不兼容。步骤很简单下载JMeter二进制包解压到一个不带空格的目录比如/opt/jmeter或D:\tools\jmeter。配置JAVA_HOME环境变量指向JDK安装目录。启动时执行bin/jmeter.shLinux/Mac或bin/jmeter.batWindows。打开界面后先看一眼菜单栏确认能正常加载。有一个细节容易被忽略JMeter启动默认分配的内存很小压测MQTT时消息量大、样本多内存不够会先卡顿然后OOM。建议修改bin目录下的JMeter启动脚本把堆内存调大。Windows的jmeter.bat里找HEAP参数改成-Xms1g -Xmx2gLinux的jmeter脚本里有JVM_ARGS同样调整堆内存上限。注意修改前先备份升级JMeter版本时这个文件会被覆盖。2.2 安装MQTT插件两条路都行JMeter原生采样器里没有MQTT需要装“MQTT Protocol Support”这个插件。安装有两条路在线安装推荐启动JMeter后在菜单栏找到“选项 → Plugins Manager”切换到“Available Plugins”搜索MQTT勾选“MQTT Protocol Support”点击底部的“Apply Changes and Restart JMeter”。插件管理器会自动下载依赖并重启JMeter全程不用手工下载jar包。离线安装如果团队网络受限、插件管理器访问不了外网可以去插件对应的发布页面手动下载jar包连同依赖包一起放到JMETER_HOME/lib/ext目录下重启JMeter。需要注意插件版本和JMeter版本的匹配关系JMeter 5.x用较新的插件包JMeter 4.x用旧版装错版本会在启动时提示“UnsupportedClassVersionError”或采样器列表里找不到MQTT。装完怎么确认成功在测试计划上右键 →“添加 → 取样器”如果能看到一个叫“MQTT Connect”或类似名称的采样器说明插件已经加载。我在实际中碰到过一次装完没生效的情况检查后发现是JMeter还开着旧进程把进程全部关掉重启就正常了。2.3 准备一个可压测的Broker环境没有Broker脚本跑不起来。本地压测最省事的方式是用Docker起一个开源MQTT Brokerdocker run -d --name mqtt-broker -p 1883:1883 mosquitto:latest这样默认监听1883端口匿名访问。如果你用的是其他Broker比如某开源MQTT服务端软件原理也是一样的确认监听端口和鉴权方式即可。本地压测时Broker和JMeter通常在同一台机器。如果是远程压测还要注意安全组或防火墙要放行1883端口。Broker的鉴权配置要先准备好测试账号的权限范围要确认覆盖到压测用的Topic。压测前先用MQTT客户端工具手工连接一次发布一条消息、订阅一个主题确认网络链路通再开始压测。否则压测时才发现连不上浪费一轮时间。还有一点必须提醒不要在没有授权的情况下对生产环境或他人管理的Broker做压测这既是职业底线也是避免把线上设备打崩的基本素养。3. 第一个MQTT压测脚本从连接、发布到订阅3.1 先搞懂三个采样器的分工安装插件后取样器列表里会多出三个MQTT采样器MQTT Connect负责建立MQTT连接配置Broker地址、端口、ClientId、KeepAlive、用户名密码、遗嘱消息等连接级参数。MQTT Publish负责向指定Topic发布一条消息配置消息内容、QoS、Retained标记。MQTT Subscribe负责订阅一个或多个Topic并把收到的消息作为采样结果记录下来。一个完整的MQTT压测脚本通常由这三个采样器组合而成。核心要理解的是MQTT连接是长连接不能像HTTP请求那样每次发消息都重新建连。如果脚本设计成“线程组循环里每次都先Connect再Publish”那每个循环都会新建TCP连接、发送CONNECT报文、完成握手这会把连接建立的消耗混入发布性能数据结果完全失真。实际做法每个线程作为“一个MQTT客户端”连接只建立一次然后用“仅一次控制器”把Connect包住Publish在循环里反复执行。这样线程数就代表设备连接数循环次数代表每台设备持续发送的消息次数。3.2 配置一个“发布型”压测脚本以最典型的场景为例模拟500个设备客户端连上Broker每个客户端持续发布消息到指定Topic。第一步建线程组在测试计划上右键 → 添加 → 线程用户 → 线程组。线程数填500Ramp-Up时间根据场景设比如60秒让500个连接在1分钟内逐步建立而不是瞬间打爆Broker。循环次数先填100代表每个连接发布100条消息。注意Ramp-Up时间太短对Broker来说等于瞬时冲击很多Broker在大量并发建连时会出现握手耗时长、内存飙升的问题梯度加压更容易暴露出临界点也更好定位问题。我一般先按 Ramp-Up 线程数/10 起步再逐步调整。第二步添加MQTT Connect采样器在线程组上右键 → 添加 → 取样器 → MQTT Connect。关键参数如下Server addresstcp://127.0.0.1:1883如果Broker用SSL改成ssl://。Timeout连接超时时间单位毫秒。我通常设3000毫秒避免Broker卡住时JMeter线程长时间挂起。ClientId用JMeter函数生成唯一值比如mqtt-client-${__threadNum}-${__UUID}保证每个线程的ClientId不重复。Clean Session先选true。如果要测会话恢复场景再配置成false并配合持久会话。Keep Alive60秒表示客户端和Broker之间保活心跳间隔。User Name / Password如果Broker开鉴权就填匿名模式留空。Will Message遗嘱消息按需配置一般压测基础能力时留空。配置完以后把这个Connect采样器拖进“仅一次控制器”里保证每个线程只连接一次。第三步添加MQTT Publish采样器在“仅一次控制器”外面添加MQTT Publish采样器。关键参数Topic name发布主题比如device/${__threadNum}/data。Payload消息内容可以直接写固定文本也可以用函数生成比如${__time(yyyy-MM-dd HH:mm:ss,)}或device-${__threadNum}-msg-${__counter}。QoS先选0或1。QoS 0性能最高QoS 1性能其次QoS 2最慢。Retained是否保留消息。普通业务场景选false除非你要测保留消息对Broker的影响。第四步加断言和查看结果树为了确认脚本是真的“压到了”Broker而不是自嗨添加响应断言同时添加“查看结果树”监听器。先跑1个线程、循环1次看采样器是否绿色通过。发布型脚本执行时Publish采样器没有响应内容看起来像是“无响应数据”但只要不报错就代表消息已进入Broker。想验证Broker确实收到了消息最简单的方法是在JMeter的同一测试计划里加一个Subscribe线程组或者用MQTT客户端工具另开一个订阅端观察消息。3.3 配置一个“订阅型”压测脚本订阅场景和发布场景关注点不同发布看的是“发出去多快”订阅看的是“Broker转发给订阅者多快、消息量多大”。订阅型脚本我一般用两个线程组订阅线程组模拟N个订阅端。每个线程建立一个MQTT连接然后添加MQTT Subscribe采样器订阅某个Topic并持续运行循环设为永远或一个大数。订阅端只负责“挂着”接收消息并记录。发布线程组模拟M个发布端。每个线程建立连接后持续向订阅的Topic发布消息。两个线程组可以同时启动也可以让发布线程组延迟启动给订阅端留出建立连接和订阅成功的时间。这个设计很关键如果发布消息时订阅端还没建立订阅对于Clean Sessiontrue的会话Broker不会保留历史消息订阅端就收不到任何消息压出来的“吞吐”自然失真。Subscribe采样器的参数很简单Topic name填要订阅的主题可以带通配符比如device//dataQoS要和发布端的QoS对应。运行后订阅采样器会把收到的消息记在响应数据里从查看结果树里能直接看到Payload内容。这也是验证“发布→Broker→订阅”这条链路是否通畅最直观的办法。注意查看结果树在压测运行期间会缓存大量响应数据对订阅型压测来说尤其致命——如果每秒收到几千条消息结果树会在几分钟内吃光JMeter内存。调试脚本时开着正式压测一定要禁用或删除它。4. 把脚本写成真实压测场景设计、参数计算和常见配置4.1 场景设计线程数、连接数、消息量怎么配很多JMeter新手会把HTTP压测的思路直接搬过来线程数设大、循环次数设大就跑。MQTT场景里有两个不同维度的压力必须先分清楚连接压力由线程数决定。每个线程如果可以持续保持一个MQTT连接那线程数就等于同时在线的设备数。消息压力由“线程数 × 单线程发布速率”决定。单线程发布速率又受循环次数和间隔时间控制。举例线程数100每个线程每秒发布1条消息对Broker造成的消息压力是100条/秒。如果改成线程数10、每个线程每秒发布10条消息压力一样是100条/秒但连接压力从100降到10。这两种场景对Broker的压力形态完全不同前者测试的是Broker在大量连接下的转发能力后者测试的是单连接高吞吐能力。设计压测场景前先想清楚你要压哪一头。如果要模拟真实业务可以用常数吞吐量定时器控制发布速率。比如计划让整组线程合计每秒发布500条消息就在Publish采样器下添加“常数吞吐量定时器”吞吐量目标设为30000每分钟30000条即每秒500条计算方式选“当前线程”。这样JMeter会自动调整线程内的请求间隔让总吞吐稳定在目标值附近不会因为JMeter跑得太快把Broker直接压死。4.2 QoS级别怎么选一个容易被低估的坑MQTT的QoS表示消息投递保障等级直接决定消息交互成本QoS含义报文交互适合场景相对性能0最多一次发布端发一次PUBLISH不等待确认传感器高频数据、允许丢失最高1至少一次PUBLISH加上PUBACK确认控制指令、账单类数据中等2恰好一次PUBLISH、PUBREC、PUBREL、PUBCOMP四次握手计费、金融级可靠性最低我见过有人把所有压测场景全设成QoS 2结果吞吐上不去还以为是Broker性能差。实际上QoS 2的消息确认链路比QoS 0多出好几倍报文Broker处理每条消息的开销大得多直接压出来的吞吐差异可能在数倍以上。如果你的业务本身用QoS 0或QoS 1压测就老老实实用对应的QoS不要为了“更可靠”盲目升档。另外要注意发布端和订阅端的QoS级别决定消息在各自链路上的保障等级两端不匹配会导致消息以较低等级投递压测结果和真实业务对标时会有偏差。4.3 用变量和函数生成动态Topic与随机Payload真实设备上报消息时不可能所有设备都发到同一个Topic。所以发布型脚本里我通常会把设备ID、时间戳、随机数拼进Topic和PayloadTopicdevice/{设备ID}/data设备ID用${__threadNum}或CSV参数化。Payload可以用${__RandomString(32,abcdefghijklmnopqrstuvwxyz0123456789,)}生成随机字符串也可以用${__time(,)}带时间戳。如果业务上有固定大小的报文需求比如模拟1KB的传感数据可以用JMeter的“取样器超时”配合固定长度的Payload模板。动态Topic和Payload不是为了看起来专业是避免压测时所有消息都命中Broker的同一热点路径。消息内容全一样时Broker内部的一些逻辑比如持久化分片、消息去重反而测不出来和线上真实混杂流量差距很大。4.4 理论吞吐量和带宽预估压测前先算一笔账可以快速判断压测机和网络会不会先于Broker成为瓶颈。公式很简单单线程消息速率r条/秒 1 / 消息间隔秒数总消息速率R 线程数 ×r带宽占用 ≈ R × 单条消息体积 × 1.2假设一个主题每秒要收500条消息每条消息Payload为1KB那带宽是 500 × 1KB × 1.2 ≈ 600KB/s。这个量对千兆网卡毫无压力。但如果压的是1MB的图片上报消息每秒100条就是100MB/s这时候瓶颈很可能在压测机网卡或Broker带宽而不是Broker的CPU。提前算好这比账能帮你判断压测结果到底是Broker不行还是压测环境不够。5. 压测执行、监控和结果分析跑起来之后的事5.1 正式压测前的验证清单脚本搭好后不要一上来就500线程冲上去。我的流程是先用1个线程、循环1次跑通脚本确认能连接、能发布、能订阅。再按目标压力的10%跑30秒比如目标500线程50条/秒就先跑50线程观察错误率、延迟确认脚本没有明显问题。确认无误后全量加压。正式压测时查看结果树一定要禁用或关闭。它会把每个响应内容存进内存MQTT订阅场景下消息量大几分钟就能把JMeter内存耗尽。需要用监听器时只保留一个聚合报告或表格查看器即可。原始结果可以勾选“保存响应数据到文件”把数据写到磁盘压完之后再分析比边压边在界面里滚动数据靠谱得多。5.2 结果数据怎么看聚合报告、吞吐、错误率聚合报告是最常用的监听器但它默认只统计采样器的执行情况对MQTT场景要重点关注几个指标Samples采样器执行次数。对Subscribe采样器来说这个值代表“收到多少条有效消息样本”而不是订阅了多少次。如果Samples远低于发布总量说明消息在Broker侧或网络上丢了。Average / Median / 90% Line / 99% Line响应时间。对Publish来说是从发出到收到确认的时间QoS 0时几乎为0对Subscribe来说是消息从Broker到订阅端的投递时延。Throughput每秒完成的样本数可以近似看作消息吞吐。Error %连接失败、发布异常、订阅超时的占比。任何非零错误都要先查日志别急着分析吞吐。我在实际压测中会同时开一个后端监听器把结果数据发到时序数据库再配可视化面板看实时曲线。这样比等压测结束再看聚合报告更直观能一边加压一边观察Broker状态什么时候开始劣化一目了然。5.3 怎么判断Broker的瓶颈压测过程不是“跑完看结果”就结束而是要边跑边判断“瓶颈在哪里”。我常用的方法梯度加压从低到高逐步增加线程数或消息速率每挡跑2~3分钟观察吞吐是否线性增长。如果消息速率到某个阈值后吞吐不再上涨、错误率开始飙升这个阈值就是当前Broker配置下的能力上限。看资源曲线压测过程中同时监控Broker所在机器的CPU、内存、网络带宽。如果CPU先打满说明Broker计算密集如果内存暴涨说明连接数或消息缓冲区超了如果网卡先到峰值说明带宽是瓶颈。对时延变化敏感MQTT消息的端到端延迟正常情况下非常稳定。如果延迟曲线开始抖动往往意味着Broker内部队列积压已经接近处理极限。定位瓶颈时记得把JMeter压测机的监控也拉出来看一眼。如果压测机本身CPU耗尽、垃圾回收频繁那测试结果会污染成“压测机太弱”而不是Broker不行。这也是要梯度加压的原因之一每个梯度下都对比JMeter端和Broker端的指标才能分清责任。6. 常见问题与排查经验实录6.1 连接失败或频繁掉线这是MQTT压测里最常见的坑现象层出不同根因却很集中。整理成一张速查表现象可能原因排查和解决Connect采样器直接报连接超时Broker地址/端口不通防火墙拦截先用MQTT客户端工具手工连接验证检查安全组、端口监听状态Broker拒绝连接用户名密码错误、ACL权限不足检查Broker鉴权配置压测账号单独开通对应Topic的读写权限连接建立后立刻被断开ClientId与其他客户端重复确保每个线程生成的ClientId唯一建议用${__threadNum}拼接或UUID压测线程数涨到几千后大量断连Broker最大连接数上限触发查看Broker连接数配置区分是Broker上限还是压测机文件描述符耗尽压测机本地大量TIME_WAIT连接短连接反复建立检查脚本是否在循环中反复Connect用仅一次控制器限制连接只建一次排查时先看Broker日志。大部分Broker会记录断开连接的原因码和ClientId能直接指向问题。比如“ClientId already in use”就是重复连接如果是鉴权问题日志里会明确报权限不足。6.2 订阅端收不到消息订阅收不到消息可能是最让人崩溃的问题。原因通常有几种Topic不匹配发布端发的device/100/data订阅端订阅的是device//telemetry通配符和实际主题层级都对不上消息自然收不到。先用最简单的方式两边都用完全一样的Topic调试通再加通配符。先发布后订阅Clean Session为true时Broker不会为离线或尚未订阅的客户端保留消息。如果发布线程先启动消息发出去了订阅端后建立订阅早就错过了。调试时让订阅线程组先启动或者给发布线程组加一个延迟启动定时器。QoS级别冲突发布端QoS 2、订阅端QoS 0Broker会按两者中较低等级投递部分场景可能出现消息不达预期。建议调试阶段两端QoS保持一致。订阅端被踢下线连接没保住订阅关系自然消失。检查ClientId是否唯一、KeepAlive是否设置合理、Broker有没有空闲断连策略。排查思路很简单先单独跑一个订阅客户端用图形工具或命令行工具订阅同一个Topic发布一条消息看订阅端收到没有。如果第三方客户端能收到说明Broker和链路没问题问题在JMeter脚本配置如果第三方客户端也收不到问题就在Broker配置或Topic本身。6.3 结果数据异常和性能偏差聚合报告里Samples数量不符先看是不是多个采样器共用了同一个名称。JMeter默认按“采样器名称 线程组”来聚合统计两个采样器都叫“Publish”会把数据混一起。给每个采样器起独立的名称比如“设备数据发布-QoS1”和“连接建立”统计才分得清。QoS 0发布采样器吞吐异常高这是正常的QoS 0不需要确认JMeter只负责把消息丢给Broker吞吐上限受压测机本身限制。想测真实网络路径上的转发能力还是要靠Subscribe端的接收数来验证。响应数据乱码或显示为十六进制MQTT消息本质是二进制插件在响应数据里显示一串十六进制是正常的。如果想看明文可以在Payload里拼时间戳或固定文本或者改用文本消息模式。分布式压测结果汇总异常多台压测机执行时每台机器的时钟会漂移聚合报告的响应时间会失真。可以用时间同步服务校准各压测机时钟或者在结果落盘后按时间戳合并分析别直接用JMeter全分布式聚合。压测期间JMeter自身卡顿、GC频繁检查是否还开着查看结果树检查JMeter堆内存是否够把不用的监听器停掉。堆内存配置至少在2G以上如果线程数过大考虑拆成多台压测机来压。6.4 Broker不授权不给压先沟通拿到书面确认这个算不上技术问题但比技术问题更值得注意。没有明确授权就去压测某个Broker无论是生产环境还是他人负责的系统都属于越权行为。我在每次压测前都会做一次确认目标Broker的负责方、压测时间窗口、压测流量上限、账号权限范围全部落到文字上。这既是对别人系统负责也是保护自己不被背锅。7. 最后分享几条压测心得压MQTT这几轮下来我最深的体会是工具永远是最容易的部分难的是搞懂协议、设计好场景、读懂数据。JMeter装个插件谁都会但一个脚本里线程数、循环次数、QoS、Clean Session、Ramp-Up这些参数怎么组合才能真正决定压出来的结果有没有参考价值。几条经验实时记录一下第一调试阶段和正式压测阶段不要用同一套监听器配置。调试时看结果树正式压测时只留落盘数据和必要的聚合报告这是避免“压测机内存被监听器吃满”最有效的办法。第二梯度加压是基本功。上来就压最大压力的测试结果通常只有一个——Broker和压测机一起崩你还不知道先崩的是谁。从10%压力开始每挡稳一段时间让数据告诉你瓶颈在哪。第三压测结果一定要结合Broker视角看。JMeter只告诉你“发出去多少、收到多少”但消息在Broker内部有没有积压、连接数曲线长什么样、内存有没有缓慢上涨这些才是判断系统健康度的关键。以后再做MQTT压测哪怕只用一台机器跑JMeter和Broker我也一定会同时开着系统监控。如果你正准备给自己的MQTT Broker做一次压测希望这篇能帮你少踩几个坑。有问题欢迎在评论里交流特别是那些你自己也查不出原因的诡异现象有时候别人的一个排查思路就能省你半天时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑