当前位置: 首页 > 测试知识 > JMeter MQTT协议插件开发与物联网压力测试
JMeter MQTT协议插件开发与物联网压力测试
2026-07-27 作者cwb 浏览次数28

在进行物联网(IoT)系统性能测试时,JMeter 是第一选择工具之一。但原生的 JMeter 并不支持 MQTT 协议,因此需要借助插件或自行开发。本文将从现有插件使用、自定义插件开发、以及大规模物联网压力测试实战三个方面,提供一套完整的解决方案。


一、MQTT插件快速上手

最常用的是根据Eclipse Paho库开发的mqtt-jmeter插件。


1. 安装

从GitHub - emqx/mqtt-jmeter的Releases页面下载最新mqtt-jmeter-xxx.jar。

将jar放入$JMETER_HOME/lib/ext/ 目录。

重启JMeter,在Sampler中能看到MQTT Connect、MQTT Pub Sampler、MQTT Sub Sampler 和 MQTT DisConnect。


2. 创建基本测试场景

一个典型的设备行为是:建立连接 - 订阅主题 - 发布消息 - 断开连接。

线程组:设置线程数模拟设备数量。


MQTT Connect:

Broker URL: tcp://broker.emqx.io:1883

ClientId: 使用 JMeter 函数 ${__threadNum} 或 ${__UUID} 保证唯一。

Keep Alive: 60s,勾选 Clean Session。

MQTT Sub Sampler(可选):订阅 test/topic,QoS 设为 1,并设置超时时间。常用作接收下行指令的证实。

MQTT Pub Sampler:主题 test/topic,QoS 1,消息体填入模拟遥测数据的 JSON。

MQTT DisConnect:释放连接。


优势:开箱即用,界面配置。

局限:无法实现复杂的连接状态机(如断线重连后的行为)、自定义负载加密、或特殊的 WebSocket 连接参数。此时就需要自定义开发。

二、自定义MQTT插件开发

开发自定义插件可实现和业务更紧密的耦合,如模拟设备影子更新、OAT升级流程等。


1. 技术选型和架构

客户端库:推荐Eclipse Paho Java(稳定、异步支持)或HiveMQ MQTT Client(性能更优,支持背压)。


开发方式:

轻量级:实现 JavaSamplerClient 接口,直接以“Java 请求”形式运行,无需编写 GUI。

专业级:继承 AbstractSamplerGui + AbstractSampler,打包成标准 JMeter 组件。

设计-连接复用:不能每个sampler都新建连接。可通过JMeterContext级别的变量存储MqttClient实例,使同一线程内的采样器共享一条TCP长连接。


2. 实战:根据JavaSamplerClient实现发布采样器


代码MqttPubClient.java:


java

import org.apache.jmeter.config.Arguments;

import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;

import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;

import org.apache.jmeter.samplers.SampleResult;

import org.eclipse.paho.client.mqttv3.*;

import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;


public class MqttPubClient extends AbstractJavaSamplerClient {

    // 用于线程内共享连接的 key

    private static final String CTX_CLIENT_KEY = "MQTT_CLIENT";

    private MqttClient client;


    // 定义采样器参数

    @Override

    public Arguments getDefaultParameters() {

        Arguments params = new Arguments();

        params.addArgument("broker", "tcp://127.0.0.1:1883");

        params.addArgument("clientIdPrefix", "perf_");

        params.addArgument("topic", "test/device");

        params.addArgument("qos", "1");

        params.addArgument("message", "{\"temp\":25}");

        params.addArgument("cleanSession", "true");

        return params;

    }


    @Override

    public void setupTest(JavaSamplerContext context) {

        // 从线程上下文获取已存在的连接,避免重复创建

        client = (MqttClient) context.getJMeterContext().getVariables().getObject(CTX_CLIENT_KEY);

        if (client == null || !client.isConnected()) {

            String broker = context.getParameter("broker");

            String clientId = context.getParameter("clientIdPrefix") + Thread.currentThread().getName();

            try {

                client = new MqttClient(broker, clientId, new MemoryPersistence());

                MqttConnectOptions options = new MqttConnectOptions();

                options.setCleanSession(Boolean.parseBoolean(context.getParameter("cleanSession")));

                options.setConnectionTimeout(10);

                options.setKeepAliveInterval(60);

                // 可在此加入 SSL、用户名密码等配置

                client.connect(options);

                // 将连接存入线程上下文,供后续采样器复用

                context.getJMeterContext().getVariables().putObject(CTX_CLIENT_KEY, client);

            } catch (MqttException e) {

                log.error("MQTT连接失败", e);

            }

        }

    }


    @Override

    public SampleResult runTest(JavaSamplerContext context) {

        SampleResult result = new SampleResult();

        result.sampleStart();

        try {

            String topic = context.getParameter("topic");

            int qos = Integer.parseInt(context.getParameter("qos"));

            String payload = context.getParameter("message");

            

            MqttMessage message = new MqttMessage(payload.getBytes());

            message.setQos(qos);

            client.publish(topic, message);

            

            result.setSuccessful(true);

            result.setResponseData("Published to " + topic, "UTF-8");

        } catch (MqttException e) {

            result.setSuccessful(false);

            result.setResponseData(e.toString(), "UTF-8");

        } finally {

            result.sampleEnd();

        }

        return result;

    }


    @Override

    public void teardownTest(JavaSamplerContext context) {

        if (client != null && client.isConnected()) {

            try {

                client.disconnect();

                client.close();

                context.getJMeterContext().getVariables().remove(CTX_CLIENT_KEY);

            } catch (MqttException e) { }

        }

    }

}


配置和运行:

将 Paho 等依赖库一同打入 jar,放入 lib/ext。

在 JMeter 中添加Java 请求,选择 MqttPubClient。

可结合 SetUp Thread Group 执行一次 MQTT Connect,再用普通线程组循环调用此发布采样器。


3. 扩展实现带回调的订阅采样器

订阅采样器需在runTest中使用client.subscribe() 并传入IMqttMessageListener,利用CountDownLatch等待指定数量的消息到达或超时,以此测量端到端下行延迟。


三、物联网压力测试实战:

1. 设计压测场景

物联网压力测试的重要标准包括:并发连接数、消息吞吐量(In/Out)、消息延迟(P99)、连接建立速率 以及Broker资源消耗。


典型场景分解:

设备上线风暴:短时间内大量设备同时发起 CONNECT。使用 Synchronizing Timer 实现集合点爆破。

心跳维持:设备连接后,每 30~60 秒发送一次小包(如 PINGREQ 或业务心跳)。用 While Controller + Constant Timer 模拟。

上行遥测:设备周期上报传感器数据。重视 Pub 吞吐和 Broker 的 messages.received。

下行指令:控制端下发命令给设备,通过共享订阅或通配符转发。注意Sub端到端延迟。


2. JMeter 压测计划创建技巧

线程组选择:Concurrency Thread Group 或 Stepping Thread Group 可以精细控制“启动-保持-停止”的节奏,如:100线程每步,每30秒增加1000,最后到达10万并发,观察系统拐点。

参数化:使用 CSV Data Set Config 为每个虚拟设备分配独立的 证书、ClientID、上报主题,模拟真实异构设备。

长连接保持:在线程组内使用 Loop Controller 无限循环,在循环内部放置 MQTT Pub 和一个 Constant Timer,保证连接永不释放。


结果收集:

开启 Aggregate Report 查看 TPS、平均/最大延迟。

使用 Backend Listener(如 InfluxDB + Grafana)实时监控,添加 mqtt 自定义度量。

如果自行开发,可在 SampleResult 中设置 Latency 为消息往返时间。


3. 压测环境调优

JMeter 客户端侧(压力机)

JVM 参数:-Xms4g -Xmx4g -XX:MaxMetaspaceSize=256m

操作系统限制:Linux 下 ulimit -n 1048576,调大临时端口范围 net.ipv4.ip_local_port_range = 1024 65535,开启 tcp_tw_reuse。

分布式:单台压力机一般能支撑 5~10 万 MQTT 连接(受限于端口和内存)。超过此数量必须使用 JMeter 分布式集群(Master + Slaves)。


Broker侧(如EMQX)

开启Dashboard的Prometheus统计。

监控 CPU、内存、Erlang 进程数、connections.count、socket.used。

Broker 同样需要调大 ulimit,调优 TCP backlog 等内核参数。


4. 分析短板

连接上不去:检查 JMeter 端 TIME_WAIT 端口耗尽,或 Broker 的 max_connections 限制。

消息延迟增大:查看 Broker 的 mailbox 积压,可能是下游订阅者消费过慢。

Broker 崩溃:一般是内存溢出,需检查客户端是不是异常导致消息风暴(如无限制的离线消息队列),可在 Broker 侧限制 最大飞行窗口 和 消息队列长度。


四、避坑指南和实践

Clean Session 的灵活运用:压测上连接时建议使用 true 减少 Broker 状态维护;测离线消息持久化时,第一个连接用 false 订阅并断开,第二个同 ClientID 再连接证实接收。

SSL/TLS 压测:必须在 JavaSamplerClient 的 setupTest 中加载 SSLSocketFactory,并注意每个连接都加载证书会造成很大的 CPU 开销,可考虑在类级别预加载 SSLContext。

消息体大小:物联网设备消息一般很小(几百字节),压测时不要只测 1KB 小包,也要按实际比例混合 10KB 固件 OTA 报文,观察 Broker 的 TCP 粘包/拆包性能。

WebSocket 模拟:某些 IoT 平台使用 MQTT over WebSocket,Paho 库原生支持,只需将 Broker URL 改为 ws://... 即可。


通过成熟的mqtt-jmeter插件可以解决80%的通用物联网压测需求,而面对私有协议认证、复杂设备影子交互等定制场景,根据Paho的JavaSamplerClient开发能提供极大的灵活性。将两者和JMeter强大的定时器、参数化、分布式能力结合,就能创建出从边缘到云端的可靠物联网性能测试方案。


文章标签: 软件测试 测试工具 压力测试
咨询软件测试