拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

MQTTX CLI 压测与数据模拟实战指南:bench 与 simulate 命令的完整用法

MQTTX CLI 压测与数据模拟实战指南:bench 与 simulate 命令的完整用法
  • 开发工具
  • 物联网
  • 后端

【免费下载链接】MQTTX

A Powerful and All-in-One MQTT 5.0 client toolbox for Desktop, CLI and WebSocket.

项目地址:https://gitcode.com/gh_mirrors/mq/MQTTX
点击查看免费下载

MQTTX CLI 内置的bench与simulate系列命令,为开发者在无需编写脚本的情况下即可完成连接、订阅、发布三个维度的负载生成与数据仿真。本文以 MQTTX CLI 1.13.0(对应本仓库cli目录)为基准,完整讲解bench conn、bench pub、bench sub、simulate、ls --scenarios的配置参数、模板语法与源码级实现细节,帮助读者正确设计有界(bounded)压测任务、理解吞吐量与消息条数的真实语义,并掌握编写自定义数据模拟场景的方法。

写在前面:压测的边界与前提

使用这些命令时,负载生成或数据模拟是明确目标。在开始之前必须先确立:目标 broker、允许使用的主题前缀、客户端数量、消息大小/发送速率、消息条数上限以及运行时长。MQTTX CLI 的默认值带有“危险”性质——bench系列默认创建1000 个连接、发布无上限(--limit 0),因此每次都应当显式提供边界。一个随手运行的快速示例并不等于容量基准测试。

下文示例均假设127.0.0.1:1883上已存在一个完成授权的 broker。还有两条必须遵守的纪律:

  • 给每个命令都包裹一个外部截止时间(deadline)。bench conn与bench sub在成功后保持活跃,不会自行退出;发布者的消息条数限制并不能覆盖“连接挂死”或“部分失败”的情形。
  • 配置文件的默认值可能通过~/.mqttx-cli/config提供 host、port、protocol 与凭据(参见 configuration.md 中关于初始化与--load-options的说明),显式 CLI 参数优先于默认值。

连接与订阅基准测试

bench conn:批量建连

mqttx bench conn -h 127.0.0.1 -p 1883 -l mqtt -V 5.0 \ --count 2 --interval 100 --client-id 'probe-conn-%i' --reconnect-period 0
  • --count是客户端数量;
  • --interval是启动相邻两个客户端连接之间的延迟,单位毫秒;
  • --client-id使用%i占位符生成互不冲突的客户端 ID。

记录实际建立的连接数量与总建连耗时,然后在截止时间点结束本次运行。从实现上看,conn.ts 中的benchConn会按count循环创建 MQTT 客户端,每建一个等待interval毫秒(await delay(interval)),并通过connectedCount累加成功连接数;当connectedCount === count时输出Created ${count} connections in ${(end - start) / 1000}s。这也解释了为什么文档要求“记录实际创建的连接数”——源码统计的是实际建连成功的数量,而非配置的--count。

bench sub:批量订阅

mqttx bench sub -h 127.0.0.1 -p 1883 -l mqtt -V 5.0 \ --count 2 --interval 100 --client-id 'probe-sub-%i' \ -t 'mqttx-test/load/%i' -q 1 --verbose --reconnect-period 0

订阅者要先于发布者启动,并同时检查订阅被拒(rejection)的情况以及“全部就绪”这一聚合状态行。需要特别警惕 1.13.0 版本的一个坑:All connections subscribed这行日志可能在只有部分订阅成功时出现。

对应实现位于 sub.ts 的benchSub:它统计subscribedCount(每个客户端、每个主题的订阅结果都会计数),只有当connectedCount === count && subscribedCount === count * topic.length时才会打印就绪状态,但打印条件是allSuccessfulSubs.length > 0,而失败订阅(QoS 返回 > 2 被拒)只是逐条输出subscriptionNegated,并不会阻止这行日志的出现。因此判断订阅是否真正成功,必须结合--verbose输出中逐条订阅的结果来综合判定。

bench sub只报告接收总数与速率,不会逐条解码并展示每条消息的 payload 内容。如果需要对 payload 做内容检查、文件保存、编解码或干净输出,应使用普通sub命令(参见 payloads.md 与 workflows.md)。

发布基准测试

bench pub:有界负载探针

mqttx bench pub -h 127.0.0.1 -p 1883 -l mqtt -V 5.0 \ --count 2 --interval 100 --message-interval 500 --limit 6 \ --client-id 'probe-pub-%i' -t 'mqttx-test/load/%i' -q 1 \ -m 'bounded load probe' --verbose --reconnect-period 0

关键参数语义(与普通pub不同,务必区分):

  • --message-interval(短选项-im):每个客户端各自的发布间隔,单位毫秒;
  • 粗略的期望速率(offered rate)≈count * 1000 / messageInterval(消息/秒),实际值还会受到 QoS 确认与调度的影响;
  • --limit(短选项-L):跨所有客户端的聚合消息条数上限,而不是每个客户端的配额。

源码 pub.ts 的multiPub直接印证了上述语义:messageInterval被传入setInterval(..., messageInterval)作为每个客户端各自的发布定时器;limit则在共享变量total上做聚合判断(limit > 0 && total >= limit即退出)。同时注意测量实际计数,不要把配置速率等同于实际吞吐。

两个实现细节值得注意:

  1. multiPub会在所有配置的客户端都连接成功(connectedCount === count时置initialized = true)之后才开始真正发布——从client.on('connect')中if (!initialized || !client.connected ...) return的逻辑可见。因此任何一个客户端建连失败都可能让整个压测任务挂起,这就是文档强调“必须保留外部 deadline”的原因。
  2. --verbose只改变统计行的输出方式(非 verbose 用交互式 Signale 行内刷新,verbose 用常规 log 逐行输出Published total: X, message rate: Y/s),每 1 秒刷新一次速率(rate每秒清零重新累加)。

消息来源:随机字节与文件回放

  • --payload-size 1KB可替代-m,生成指定大小的随机字节。底层 payloadGenerator.ts 支持B/KB/MB/GB单位(如1024B、1KB、2.5MB),超过 MQTT 单条 256MB 上限(MQTT_SINGLE_MESSAGE_BYTE_LIMIT)会直接报错退出;生成逻辑使用crypto.randomBytes。
  • --file-read ./messages.txt --split:把文件按换行符切分,并为每个客户端回放切分后的每一段(源码中每个连接会_.cloneDeep(splitedMessageArr)一份独立副本,见 pub.ts)。
  • 显式传--split 'PATTERN'时,该模式被解释为JavaScript 正则表达式;末尾分隔符可能产生空消息。同样要保留外部 deadline 并检查汇总计数:客户端进度不均可能导致 split 模式无法自然完成。split 模式下 CLI 会在splitLimit = 切分条数 * count达到后自动退出(打印All N messages from the file have been successfully sent)。
  • 不带--split时,整个文件作为重复发送的 payload。
  • bench pub不接受普通pub的--stdin、--format、Protobuf 或 Avro 标志;若需要这类编码后的字节流,请提前把编码结果写入文件再读取。

主题与客户端模板

压测/模拟场景下的模板替换规则与普通命令存在差异,容易踩坑:

  • 客户端 ID 用--client-id(短选项-I)。注意短选项-i是连接间隔(interval),与普通conn/pub/sub中-i的含义完全不同。
  • %i是从 1 开始的客户端索引;客户端 ID 中若没有%i且count > 1,会自动追加_index后缀。这正是 getBenchClientId.ts 的实现:count > 1 && !hasPlaceholder ? \${baseClientId}_${index}` : baseClientId`。
  • 基准测试的主题模板支持%i、%u(已提供的用户名)和%c;模拟(simulate)还额外支持%sc(场景名)。模板请在 shell 中加引号,防止被 shell 展开。
  • 1.13.0 的一个易错点:bench pub与simulate用基础客户端 ID 模板替换%c(源码中topicName.replaceAll('%c', clientId),此处clientId是未展开的原始模板),而bench sub用的是最终按客户端展开后的 ID。因此要保证发布者与订阅者主题对齐,应显式使用%i主题模板,不要假设%c在两条路径上展开成同一个值。%u在没有提供用户名时保持不替换(源码中username && (topicName = topicName.replaceAll('%u', username)))。

内置模拟场景与列出命令

ls --scenarios:列出场景

mqttx ls --scenarios

ls只有在提供--scenarios(短选项-sc)时才列出内置场景;它不会枚举 broker 的主题、客户端或保留消息。实现见 ls.ts:动态读取cli/src/scenarios目录下所有.js文件,以表格形式输出每个场景的name与description。以实际安装后ls --scenarios的输出为准,不要假设某个特定版本一定带了哪些场景。

本仓库当前内置四个场景(文件名区分大小写,与 scenarios 目录 一一对应):

场景文件数据形态
weatherweather.ts模拟天气站 JSON 数据,含季节/昼夜感知的温湿度、风速、气压、空气质量等字段
teslatesla.ts模拟特斯拉车辆遥测 JSON,含车辆状态、电量、行程、温控等字段
smart_homesmart_home.ts智能家居场景数据
IEMIEM.tsIEM 场景数据

以weather场景为例,其generator会为每个clientId缓存一份静态信息(station_id、城市、经纬度等),再叠加随时间/季节/昼夜变化的实时字段,最终返回{ message: JSON.stringify(data) }(见 weather.ts)。

simulate:运行模拟

mqttx simulate --scenario weather -h 127.0.0.1 -p 1883 -l mqtt -V 5.0 \ --count 1 --interval 100 --message-interval 500 --limit 3 \ --client-id 'probe-sim-%i' -t 'mqttx-test/sim/%sc/%i' -q 1 \ --verbose --reconnect-period 0

正确的操作顺序是:先用ls --scenarios发现场景 → 在目标主题上启动一个有界订阅者 → 再运行 simulate,随后校验实际收到的消息条数与场景字段。

要点:

  • 模拟只负责生成 payload,它不会创建设备、也不会配置 broker;
  • simulate与bench pub共享同一套 count/rate/limit 语义(两者在源码中都走multiPub,仅通过commandType区分,见 pub.ts),并支持连接类与 MQTT 属性类选项;
  • simulate没有通用的--message、--format或--payload-size输入标志——消息内容完全由场景生成器决定。

自定义场景脚本

用 --file 代替 --scenario

simulate --file ./probe.js(注意:在普通pub/sub中-f表示 format,这里语义不同)可以加载自定义场景。脚本是可执行的 JavaScript,会被直接加载进 CLI 进程执行,因此:

  • 运行前必须审查用户提供的脚本,并严格遵守用户要求的主题与负载范围;
  • 场景来源(--scenario与--file)只能二选一。

加载机制见 simulate.ts:loadSimulator校验文件扩展名必须为.js、generator必须是函数,然后把 CLI 内置的 Faker 实例自动注入为第一个参数(simulatorModule.generator(faker, options))。若--file与--scenario同时相关,--file优先。

generator 的契约

一个 CommonJS 上下文(即目录未被配置为"type": "module")中的.js模块可以这样写:

module.exports = { name: 'probe', description: 'Small sensor readings for an MQTT test', generator(faker, options) { return { message: JSON.stringify({ device: options.clientId, value: faker.number.int({ min: 0, max: 100 }), }), } }, }

必须遵守的约定:

  • generator(faker, options)是必需的同步函数,返回{ message, topic? };message必须是字符串或 Buffer;
  • CLI 注入其内置的 Faker 实例以及当前客户端的 options(options.clientId可拿到展开后的客户端 ID);
  • 导出name,这样%sc才有有意义的取值(simulate中用%sc替换场景名,见 pub.ts);
  • 可选的author、version、description、dataFormat字段用于描述场景元数据;
  • 返回的topic会覆盖 CLI 的主题模板——发布前务必审查这一返回值;
  • 不要返回 Promise,也不要把原始 JavaScript 对象直接当作 MQTT payload(对象不会被自动序列化)。

运行自定义脚本:

mqttx simulate --file ./probe.js -h 127.0.0.1 -p 1883 -l mqtt -V 5.0 \ --count 1 --interval 100 --message-interval 500 --limit 3 \ --client-id probe-custom -t 'mqttx-test/sim/%sc' -q 1 --reconnect-period 0

版本相关的已知坑与结果报告

  • 1.13.0 的模拟命令对外宣传的是拼写错误的--maximun-reconnect-times,但运行时读取的实际字段是maximumReconnectTimes(源码中const { maximumReconnectTimes } = options贯穿conn/pub/sub/bench全链路,见 conn.ts、pub.ts)。不要依赖那个错误拼写的标志来结束一次有限运行——一次性测试请使用--reconnect-period 0,并强制设置墙钟截止时间;只有场景确实需要时才保留重连。
  • 报告时应当区分并同时给出:请求值与实际达成值(客户端数、消息条数、QoS、payload 大小、耗时、观察到的速率/错误),以及清理情况。
  • 订阅者的送达总数可以大于发布者的发布总数——当多个订阅者各自收到同一份发布时这是正常的扇出现象,不代表发布者重复发消息。判断重复只能基于发布侧计数与订阅侧去重比对,不能仅凭数字大小下结论。

小结

MQTTX CLI 的压测与模拟链路在设计上高度一致:bench conn/bench sub/bench pub与simulate共享 count/interval/message-interval/limit 语义,模板系统统一支撑%i、%u、%c、%sc占位符,模拟场景则统一收敛到generator(faker, options)这一契约。牢牢记住三个原则即可安全使用:始终显式设置边界(连接数、速率、条数、deadline)、以实际计数而非配置值为准、先审查脚本与主题模板再运行。更多与bench/simulate配合使用的命令(连接配置、payload 编解码、故障诊断)可继续阅读本技能包的 workflows.md、connections.md 与 troubleshooting.md。

  • 开发工具
  • 物联网
  • 后端

【免费下载链接】MQTTX

A Powerful and All-in-One MQTT 5.0 client toolbox for Desktop, CLI and WebSocket.

项目地址:https://gitcode.com/gh_mirrors/mq/MQTTX
点击查看免费下载
上一篇:ES6粘性匹配终极指南:如何用y标志提升正则表达式性能300%
下一篇:NSwag文档团队协作报告:生成协作统计报告

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表