测试环境
日期:2026 年 7 月 24 日 · 协议:MQTT 3.1.1 · 工具:自研负载客户端 cmd/loadtest,基于本项目自己的编解码器,未使用第三方 MQTT 库。
为把代理服务器本身的表现与环境的影响区分开,测试在两套环境上进行:经网络与走回环,普通机器与高性能机器。
| 环境 A —— 经网络 | 环境 B —— 回环 | |
|---|---|---|
| 代理服务器 CPU | 12 核 | 2× Xeon E5-2690 v4 @ 2.6 GHz(28 核 / 56 线程) |
| 代理服务器内存 | 16 GB | 128 GB |
| 地址 | 192.168.1.84:1883(局域网) | 127.0.0.1:1883(回环) |
| 负载生成端 | 局域网上另一台机器 | 与代理服务器同一台机器 |
| 客户端临时端口 | 16,384(默认) | 55,000(已放宽) |
如何看待这份对比。两套环境并不等价,回答的问题也不同。环境 A 展示的是经由网络、并有专用生成机时的表现。环境 B 去掉了网络(时延几乎为零)并给代理服务器配了强力硬件,但生成端与代理服务器共用同一批核心。因此时延和扇出可以正面比较,而接收速率不能:在环境 B 上,客户端是在跟代理服务器抢核心。
结论概要
- 时延。常规负载下「发布者 → 代理服务器 → 订阅者」的端到端时延,经网络约为 1 毫秒;走回环为 73 µs(去掉网络后低约 16 倍)。
- 连接数。经网络时代理服务器稳住了 10,000 条会话且零失败(再往上就撞到客户端的端口上限)。在强力硬件上走回环:30,000 条零失败,冲击 50,000 时留下 约 41,000(这是单台机器的极限,而不是代理服务器的)。
- 接收量。经网络的峰值约为 每秒 277,000 条消息;走回环时较低(每秒约 185,000),因为生成端在与代理服务器争抢核心。
- 扇出(分发)。在强力硬件上,代理服务器向订阅者投递了 每秒约 1,350,000 条消息(扇出 ×100),而经网络时约为每秒 22,000 条。
- 稳健性。两套环境下代理服务器都没有崩溃也没有卡死。过载时,发往落后订阅者的 QoS 0 消息会被有序丢弃;所有测试中代理服务器的日志里没有一条错误。
并发连接容量
每条连接都是一次带认证的完整 CONNECT → CONNACK 流程,随后保持会话打开(用 PINGREQ 核实)。
环境 A —— 经网络
| 目标 | 已建立 | 失败 | 建立 p50 | p99 |
|---|---|---|---|---|
| 10 | 10 | 0 | 146 ms | 162 ms |
| 100 | 100 | 0 | 702 ms | 1.12 s |
| 1,000 | 1,000 | 0 | 4.21 s | 9.78 s |
| 5,000 | 5,000 | 0 | 4.64 s | 8.59 s |
| 10,000 | 10,000 | 0 | 4.92 s | 10.52 s |
| 15,000 | 14,879 | 121 | 7.43 s | 14.09 s |
环境 B —— 回环(逐步加压)
| 目标 | 已建立 | 失败 | 建立 p50 | p99 |
|---|---|---|---|---|
| 1,000 | 1,000 | 0 | 886 ms | 1.06 s |
| 10,000 | 10,000 | 0 | 903 ms | 1.55 s |
| 30,000 | 30,000 | 0 | 923 ms | 1.90 s |
| 50,000 | 41,219 | 0 | 984 ms | 1.90 s |
分析
- 经网络时到 10,000 为止零失败;15,000 时的 121 次失败,是客户端临时端口(16,384)耗尽所致。那是生成端的极限,不是代理服务器的。
- 走回环时,把端口范围放宽之后,代理服务器完好地维持了 30,000 条会话;冲到 50,000 时,保持期结束时仍有约 41,000 条存活——毕竟两端共用一台机器。整个过程中代理服务器的日志始终干净,没有错误。
- 附带发现。走回环时,连接建立得太猛(同时发起 1,000 次
dial)会触发connection refused:接受队列被挤爆了,因为没有网络时延,所有 SYN 会一齐涌到。逐步加压(每次 200 条)能完全消除这一现象。经由网络时,时延本身就把这个尖峰抹平了。
接收吞吐
一组发布者以尽可能快的速度发布,测量代理服务器的接收速率。
QoS 0 · 64 字节载荷
| 发布者 | 环境 A(网络) | 环境 B(回环) |
|---|---|---|
| 10 | 271,006/秒 | 185,012/秒 |
| 100 | 236,422/秒 | 101,457/秒 |
| 500 | 103,546/秒 | 125,324/秒 |
经网络时接收量更高,并不是因为环境 A 的代理服务器更快,而是因为那里的生成端跑在另一台机器上。走回环时客户端与代理服务器共用同一批核心,因此总速率被压住了。
QoS 1(带 PUBACK 确认)· 64 B
| 发布者 | 环境 A(网络) | 环境 B(回环) |
|---|---|---|
| 10 | 10,292/秒 | 63,606/秒 |
| 100 | 22,085/秒 | 48,983/秒 |
QoS 1 的测试客户端是同步的(每发一条就等一次 PUBACK),因此受制于往返时间。走回环时往返几乎为零,所以快了约 6 倍。这清楚地说明,QoS 1 的数字是由网络时延决定的,而不是代理服务器。
不同消息长度 · QoS 0 · 50 个发布者
| 长度 | 环境 A(网络) | 环境 B(回环) |
|---|---|---|
| 64 B | 277,198/秒 · 17.7 MB/秒 | 101,961/秒 · 6.5 MB/秒 |
| 256 B | 124,997/秒 · 32.0 MB/秒 | 102,029/秒 · 26.1 MB/秒 |
| 1,024 B | 38,458/秒 · 39.4 MB/秒 | 102,151/秒 · 104.6 MB/秒 |
两个不同的天花板:
- 经网络时在每秒约 40 MB 处撞到链路上限:消息越大,每秒条数越低,而 MB/秒 一直贴着上限。
- 走回环时链路极宽,于是消息条数保持稳定(50 条连接下每秒约 102,000),而流量升到每秒 104.6 MB。这里的瓶颈是每条消息的处理开销(CPU),而不是字节数。
端到端投递
在消息体里嵌入时间戳,测量真实的投递量和端到端时延。扇出指的是投递给每一个过滤器匹配的订阅者。
环境 A —— 经网络
| 场景 | 发布 | 投递 | 平均 / p50 / p99 |
|---|---|---|---|
| 10 订阅 / 10 发布 / 每秒 100 条 —— 常规 | 986/秒 | 9,863/秒 | 1.17 / 1.0 / 3.6 ms |
| 100 订阅 / 10 发布 / 不限速 | 261,551/秒 | 22,351/秒 | 780 / 453 / 678 ms |
| 100 订阅 / 10 发布 / 每秒 500 条 | 4,934/秒 | 12,150/秒 | 1.87 / 1.77 / 2.83 s |
| 1,000 订阅 / 5 发布 / 每秒 100 条 | 494/秒 | 178,961/秒 | 2.29 s / 480 / 952 ms |
环境 B —— 回环
| 场景 | 发布 | 投递 | 平均 / p50 / p99 |
|---|---|---|---|
| 10 订阅 / 10 发布 / 每秒 100 条 —— 常规 | 987/秒 | 9,867/秒 | 73 / ~0 / 555 µs |
| 100 订阅 / 10 发布 / 不限速 | 47,557/秒 | 1,353,431/秒 | 514 / 135 / 251 ms |
| 100 订阅 / 10 发布 / 每秒 500 条 | 3,608/秒 | 360,834/秒 | 17.1 / 10.5 / 71.6 ms |
| 1,000 订阅 / 5 发布 / 每秒 100 条 | 494/秒 | 183,746/秒 | 4.73 / 4.54 / 15.1 ms |
分析
- 常规负载下的时延:经网络 1.17 毫秒,走回环 73 µs。差的就是那段网络路径。
- 负载下的分发:强力机器向订阅者投递了每秒约 135 万条消息(扇出 ×100)——比网络环境(每秒 2.2 万条)高出两个数量级,那边的代理服务器和链路都不大。
- 在大扇出的场景里,生成端本身也成了瓶颈(所有订阅者都在一台机器的一个进程里),因此投递量的峰值应当看作代理服务器能力的下限。
两套环境对照
| 指标 | 环境 A —— 网络(12 核) | 环境 B —— 回环(28 核) |
|---|---|---|
| 零失败的连接数 | 10,000 | 30,000 |
| 连接数峰值 | 14,879(客户端上限) | 约 41,000(单机上限) |
| 接收 QoS 0(64 B)峰值 | 271,006/秒 | 185,012/秒 |
| 接收 QoS 1(64 B)峰值 | 22,085/秒 | 63,606/秒 |
| 流量上限 | 每秒约 40 MB(链路) | 每秒约 105 MB(CPU) |
| 时延(常规)中位数 | 1.0 毫秒 | 约 0(平均 73 µs) |
| 扇出投递峰值 | 22,351/秒 | 1,353,431/秒 |
| 代理服务器日志中的错误 | 无 | 无 |
方法上的保留意见
- 接收速率不能在两套环境之间直接比较:环境 B 中生成端与代理服务器共用核心,环境 A 中生成端是另一台机器。
- 两套环境的负载都来自单台客户端机器;在某些场景下(大扇出、数万条连接),瓶颈是这台机器而不是代理服务器。
- QoS 1 的测试客户端是同步的——因此 QoS 1 的数字低估了代理服务器的实力。
- 测试期间没有采集代理服务器端的系统指标(CPU 与内存);服务器的状态是从它的日志(没有错误)和客户端一侧的表现推断出来的。
如何复现
每一轮测试都由同一个 loadtest 可执行文件发起;下面是三种模式的命令。
go build -o loadtest ./cmd/loadtest
# 连接容量(逐步加压与接受队列)
./loadtest -addr HOST:1883 -user myelx -pass *** \
-mode conn -levels 1000,10000,30000,50000 -hold 3s -dial-concurrency 200
# 接收吞吐
./loadtest -addr HOST:1883 -user myelx -pass *** \
-mode pub -pubs 10 -qos 0 -payload 64 -duration 10s
# 端到端投递与时延
./loadtest -addr HOST:1883 -user myelx -pass *** \
-mode pubsub -subs 10 -pubs 10 -qos 0 -rate 100 -payload 64 -duration 10s