- 物联网
- 消息队列
- 后端
【免费下载链接】mosquitto
Eclipse Mosquitto - An open source MQTT broker
导读
本文以 Eclipse Mosquitto 官方 2010 年 6 月发布的 version 0.7 发布说明 为骨架,结合当前仓库源码(src/conf.c、src/mux_poll.c、src/net.c、src/signals.c 及 ChangeLog.txt 中的 0.7 条目),逐项剖析该版本引入的核心能力:用poll()取代select()突破 1024 客户端上限、新增max_connections连接限制、SIGUSR2 触发数据库 VACUUM、mosquitto_pub支持零长度消息、客户端-d调试选项等。读完本文,你将理解 Mosquitto 高并发网络层设计的早期演进脉络,并能把其中的配置项、信号机制与命令行选项直接用于现代版本的运维与排障。
说明:0.7 发布于 2010 年,仓库中的现代版本在保留这些设计思想的同时,已将部分细节演进(例如网络层增加了 epoll/kqueue 分支,SIGUSR2 的职责由数据库 VACUUM 演变为打印订阅树)。文中会明确标注每个特性在当前源码中的对应实现与差异。
版本背景:为什么 0.7 被单独作为一个发布
发布说明开篇即点明:0.7 是"新特性发布"(a new features release),虽然变更项数量相对较少,但网络 socket 处理方式发生了重大重构(to allow >1024 clients),这是其被作为独立版本发布的主要原因。
在 0.7 之前,Mosquitto 的网络事件循环基于select()系统调用。select()有两个著名瓶颈:一是 fd 上限通常被编译为FD_SETSIZE(典型值 1024),超出即无法工作;二是每次调用都需要内核线性扫描全部 fd 集合,连接数越多性能衰减越明显。0.7 的核心任务就是把网络层从select()迁移到poll(),从而解除单进程 1024 个并发连接的硬限制。
从 ChangeLog.txt 可以看到 0.7 的完整变更清单与本文发布说明完全对应,包括 bug 修复项,说明这是一次"特性 + 稳定性"并重的发布。
特性一:以 poll() 取代 select(),突破 1024 客户端上限
发布说明第一项变更即为:
Use poll() instead of select() to allow >1024 clients.
这一选择的工程价值体现在当前仓库的 src/mux_poll.c 中。该文件是整个 poll 事件循环的实现,其关键设计如下:
- 使用动态分配的
struct pollfd *pollfds数组管理全部被监视的 socket,数组大小不再受FD_SETSIZE限制,而是取决于系统上限:Windows 下取_getmaxstdio(),Unix 下取sysconf(_SC_OPEN_MAX)(见 src/mux_poll.c); - 每个连接注册
POLLIN读事件,主循环调用poll(pollfds, pollfd_current_max+1, timeout)等待就绪事件,再按revents & POLLIN分发处理(src/mux_poll.c); - 循环中对所有就绪 fd 批量处理,配合下面的"accept() 全部可用连接"优化,形成高并发下的高效收包路径。
从源码结构看:poll 只是 Mosquitto 多路复用架构的第一代
从当前仓库结构可以推断,0.7 引入的 poll 抽象后来被进一步演化为统一的多路复用层 src/mux.h,并派生出三个后端:
| 后端 | 源文件 | 适用场景 |
|---|---|---|
| poll | src/mux_poll.c | 通用可移植实现,仅当未启用 epoll/kqueue 时编译(见文件开头的#if !defined(WITH_EPOLL) && !defined(WITH_KQUEUE)) |
| epoll | src/mux_epoll.c | Linux 大规模连接场景,注册EPOLLIN/EPOLLOUT事件(src/mux_epoll.c) |
| kqueue | src/mux_kqueue.c | BSD/macOS 场景 |
因此,0.7 的 poll 迁移既是当时解除 1024 连接瓶颈的关键一步,也是后来 epoll/kqueue 高性能后端的基础。
特性二:实现 max_connections 连接数限制
0.7 的另一项直接与高并发相关的特性是max_connections。发布说明原文:
Implement max_connections.
在当前仓库中,该特性已细化为两个层级的连接限制:
监听器级:per-listener max_connections
在配置解析处 src/conf.c,max_connections是listener块的子选项,通过conf__parse_int解析,负值被归一化为-1(表示不限制):
}else if(!strcmp(token, "max_connections")){ if(conf__parse_int(&token, token, &cur_listener->max_connections, &saveptr)) return MOSQ_ERR_INVAL; if(cur_listener->max_connections < 0) cur_listener->max_connections = -1;每个监听器结构体中的默认值在 src/listeners.c 初始化为-1:
listener->max_connections = -1;全局级:global_max_connections
配置文件中另有global_max_connections选项,默认值在 src/conf.c 同样为-1:
config->global_max_connections = -1;两者都存放在 broker 内部数据结构中(src/mosquitto_broker_internal.h 与 src/mosquitto_broker_internal.h)。
拒绝逻辑:新连接建立时的双重检查
在 src/net.c 的 accept 路径中,新连接一旦超过任一限制即被拒绝并记录 NOTICE 日志:
if((new_context->listener->max_connections > 0 && new_context->listener->client_count > new_context->listener->max_connections) || (db.config->global_max_connections > 0 && HASH_CNT(hh_sock, db.contexts_by_sock) > (unsigned int)db.config->global_max_connections)){ ... log__printf(NULL, MOSQ_LOG_NOTICE, "Client connection from %s denied: max_connections exceeded.", new_context->address);同样的检查也存在于 WebSocket 接入路径 src/websockets.c,说明该限制对 TCP 与 WebSocket 两类客户端一视同仁。
配置文件中的用法(现代版本)
结合 mosquitto.conf 与 man 手册 mosquitto.conf.5.xml 的说明,0.7 引入的这一机制在现代配置中写作:
# 全局限制:所有客户端总连接数 global_max_connections 1000 # 监听器级限制:仅限制该 listener 的并发连接 listener 1883 max_connections 500-1或不设置表示不限制。该特性让运维者可以在 DNS/防火墙之外,于 broker 进程内直接为每个接入点设置容量护栏。
特性三:SIGUSR2 触发内存数据库 VACUUM
发布说明原文:
Run VACUUM on in-memory database on receiving SIGUSR2.
背景是 0.7 时代 Mosquitto 使用内存数据库(基于 SQLite)管理持久化状态,长时间运行后数据库碎片化会浪费内存,因此引入 SIGUSR2 信号作为运维指令:向 broker 进程发送SIGUSR2即触发对内存数据库执行VACUUM(SQLite 的碎片整理与空间回收命令)。
从当前源码结构看,这一"用信号驱动内部管理动作"的设计被保留并泛化:信号处理仍集中在 src/signals.c,handle_signal将 SIGUSR2 置位为flag_tree_print(src/signals.c),主循环通过signal__flag_check()消费标志并打印订阅树(src/signals.c)。同时 SIGUSR1 被用于持久化备份(flag_db_backup,见 src/signals.c),SIGHUP 用于热重载配置,SIGRTMIN 用于日志轮转(src/signals.c)。
由此可以推断 0.7 的 VACUUM-on-SIGUSR2 正是这套"信号 → 标志位 → 主循环消费"机制的最早用例之一,其工程模式沿用至今。运维启示:向 Mosquitto 进程发信号是一种受控的内部维护手段,现代版本中:
SIGUSR1:触发持久化数据库备份(需编译 WITH_PERSISTENCE);SIGUSR2:打印订阅树(当前版本实现);SIGHUP:重新加载配置、证书与安全插件;SIGRTMIN:日志轮转。
Windows 平台则通过命名事件(mosq<pid>_shutdown、mosq<pid>_reload、mosq<pid>_backup)模拟同类操作,见 src/signals.c。
特性四:mosquitto_pub 支持零长度(null)消息
发布说明原文:
mosquitto_pub can now send null (zero length) messages.
在 MQTT 中,消息 payload 允许为空(零长度),常用于"通知/触发"类语义——客户端只关心事件发生与否,不需要携带数据。0.7 之前mosquitto_pub无法发送这种空消息,0.7 补齐了这一能力。
现代mosquitto_pub的命令行接口(client/pub_client.c)依然完整支持该场景:不传-m参数、或显式使用-n选项即可发布零长度消息。相关用法也可参考 man 手册 mosquitto_pub.1.xml:
# 发布一条空 payload 的消息到主题 notifications/alarm mosquitto_pub -h localhost -t notifications/alarm -n这一特性使 Mosquitto 客户端工具链能够完整覆盖 MQTT 规范中"空消息"这一合法报文形态,为无状态触发型应用提供了最轻量的发布方式。
特性五:pub/sub 客户端新增 -d 调试输出选项
发布说明原文:
Add option to print debug messages in pub and sub clients.
该选项在客户端参数表中登记为(见 client/args.txt):
d - PUB,RR,SUB (debug log)即-d/--debug对mosquitto_pub、mosquitto_rr、mosquitto_sub三个客户端同时生效。选项解析实现在 client/client_shared.c:
}else if(!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")){ cfg->debug = true;对应状态字段为cfg->debug(client/client_shared.h)。各客户端在用法输出中都会打印该选项说明,例如 client/pub_client.c:
printf(" -d : enable debug messages.\n");启用后,客户端会输出 MQTT 协议交互的调试日志(如 client/pub_client.c 与 client/sub_client.c 的cfg.debug分支),帮助定位握手、订阅、发布等环节的协议问题。mosquitto_sub中调试输出还受--quiet压制,两者组合可以精细控制终端噪音:
# 订阅并打印完整调试信息 mosquitto_sub -h localhost -t '#' -v -d # 静默模式,仅输出消息 payload mosquitto_sub -h localhost -t '#' -q 1 --quiet特性六:$SYS/broker/changeset 导出 hg 修订号
发布说明原文:
hg revision is now exported via $SYS/broker/changeset
0.7 时代 Mosquitto 使用 Mercurial(hg)做版本控制,因此新增$SYS/broker/changeset这个$SYS主题,将当前编译的 hg 修订号暴露给订阅者,便于线上环境核对 broker 的确切构建版本。$SYS主题体系至今仍是 Mosquitto 的运维观测核心,当前仓库在 src/sys_tree.c 中维护了完整的$SYS/broker/...指标树,例如:
$SYS/broker/clients/total、$SYS/broker/clients/connected、$SYS/broker/clients/disconnected$SYS/broker/messages/received、$SYS/broker/messages/sent$SYS/broker/bytes/received、$SYS/broker/bytes/sent$SYS/broker/publish/bytes/received、$SYS/broker/packet/out/count$SYS/broker/heap/current、$SYS/broker/heap/maximum
需要指出的是,从 ChangeLog.txt 与 2015 年的 1.4 发布说明 可以看到,$SYS/broker/changeset在 1.4 中被移除,官方解释是"该主题本意用于调试"。因此该特性属于 0.7~1.3 时代的运维工具,读者在现代版本中应改用官方版本发布渠道或mosquitto -h输出来确认版本。
特性七:编译期开关——禁用堆内存跟踪
发布说明原文:
Add compile time option to disable heap memory tracking.
Mosquitto 在很长一段时间内置了堆内存分配跟踪(用于检测泄漏与统计$SYS/broker/heap/*指标),但跟踪本身有性能开销。0.7 提供编译期选项允许关闭它,为追求极致性能或内存受限的部署留出余地。
从当前仓库结构可以推断,这一思路体现在构建系统对内存模块的可选编译上:WITH_MEMORY_TRACKING等相关宏控制 lib/memory_mosq.c 等文件的编译路径,构建选项集中管理于根目录 CMakeLists.txt 与 config.mk。对编译期特性的完整选项清单,可查阅 README-compiling.md。这一"跟踪与性能可权衡"的设计哲学,后来也延续到$SYS指标采集等可裁剪功能上。
修复项详解:0.7 的稳定性补强
发布说明后半部分是七项 bug 修复,每一项都对应明确的运维或协议正确性问题,逐一解读如下。
1. 不再为断连的高 QoS 订阅者存储 QoS=0 消息
Don't store QoS=0 messages for disconnected clients with subscriptions of QoS>0.
MQTT 语义中,QoS=0 消息"最多一次"送达,本就允许丢失;broker 为断连客户端离线存储消息只应覆盖 QoS=1/2 语义。此前的实现错误地把 QoS=0 消息也排队给离线订阅者(即使其订阅的是 QoS>0),既浪费内存又违反协议语义。0.7 修正后,离线队列只承载需要可靠投递的消息。
2. accept() 全部可用连接而非每次只接受一个
accept() all available sockets when new clients are connecting, rather than just one (performance advantage)
这是与 poll() 迁移配套的高并发优化:旧实现每次事件循环至多 accept 一个连接,在突发连入场景下会反复唤醒循环;0.7 改为一次性 accept 掉当前 backlog 中的全部新连接,显著降低高并发握手时的系统调用次数。这一点在当前 src/net.c 的 accept 路径中仍有体现(新连接到达即批量处理并做max_connections检查)。
3. 客户端超时断开时发送 Will 遗嘱消息
Send Will when client exceeds keepalive timer and is disconnected.
MQTT 遗嘱(Will)语义要求:客户端异常断开(包括 keepalive 超时被 broker 判定死亡)时,broker 必须代为发布其遗嘱消息。0.7 修复了 keepalive 超时路径上遗嘱未发送的问题。当前仓库中 keepalive 检查与断开处理位于 src/keepalive.c 与 src/handle_disconnect.c,遗嘱发送相关逻辑贯穿 src/mosquitto_broker_internal.h 中的 will 处理流程。
4. 发送遗嘱前先检查客户端是否确实配置了遗嘱
Check to see if a client has a will before sending it.
与上一条配套:避免对未设置遗嘱的客户端走空发送流程,减少无效操作与潜在状态错误。
5. 正确处理多个客户端使用同一 Client ID 重复连接
Correctly deal with clients connecting with the same id multiple times.
MQTT 要求同一 Client ID 只能有一个在线会话,新连接应接管旧连接。0.7 修复了重复 ID 场景下的状态清理,防止连接表错乱。这在现代版本中对应"客户端接管(take over)"机制,相关测试见 test/broker/01-connect-take-over.py。
6. 修复桥接 keepalive 超时与重连
Fix bridge keepalive timeouts and reconnects.
桥接(bridge)模式下 broker 之间互为客户端,keepalive 与重连逻辑必须与普通客户端一致。0.7 修复了桥接链路的超时误判与重连失败问题。桥接实现位于 src/bridge.c,现代版本通过bridge配置块管理远程 broker 连接,相关文档见 mosquitto.conf 中的 bridge 配置段。
7. Windows 下不再尝试丢弃 root 权限
Don't attempt to drop root privileges when running on Windows as this isn't well supported (bug #586231).
Unix 下 Mosquitto 以 root 启动后会主动降权到mosquitto用户;但 Windows 没有等价的权限模型,强行执行会失败(bug #586231)。0.7 起在 Windows 构建中跳过该步骤,保证 Windows 平台的启动稳定性。
从 0.7 到现在的演进总结
综合发布说明与当前仓库,可以总结 0.7 的长期影响:
- 网络层:poll() 引入的多路复用抽象演化为 src/mux.h 统一接口下的 poll/epoll/kqueue 三后端,高并发能力持续增强;
- 连接管理:
max_connections/global_max_connections至今仍是 src/net.c 与 src/websockets.c 的准入控制核心; - 信号机制:SIGUSR2 从"内存库 VACUUM"演变为"打印订阅树",但"信号→标志→主循环消费"的架构沿用至今(src/signals.c);
- 客户端工具:零长度消息发布(
-n)与-d调试输出已成为 mosquitto_pub.1.xml、mosquitto_sub.1.xml 的标准能力; - 运维观测:
$SYS指标树从单一 changeset 扩展为完整的 broker 运行指标集合(src/sys_tree.c)。
对于希望深入学习或自行构建的读者,可继续阅读 README-compiling.md 了解编译期选项(包括与堆内存跟踪相关的开关),并结合 ChangeLog.txt 追踪后续版本对上述特性的每一次修正与增强。
附注:原发布说明末尾提到的源码/二进制下载页为当年官网的
/download页面,该页面不在当前仓库内,故本文不提供链接;二进制包获取方式请参阅仓库根目录 README.md 或 docker/README.md 中的安装指引。
- 物联网
- 消息队列
- 后端
【免费下载链接】mosquitto
Eclipse Mosquitto - An open source MQTT broker
相关推荐
Eclipse Mosquitto 1.4.11 发布解析:Broker、客户端与库的 Bugfix 详解
Eclipse Mosquitto 1.4.11 发布解析:Broker、客户端与库的 Bugfix 详解 Mosquitto 1.4.11 是 1.4.x 系
物联网消息队列后端网络/通信Eclipse Mosquitto 1.6 版本发布解读:MQTT v5 支持、性能优化与新客户端工具
Eclipse Mosquitto 1.6 版本发布解读:MQTT v5 支持、性能优化与新客户端工具 本文以 Mosquitto 项目 1.6 版本的官方发布
后端消息队列消息路由Mosquitto 1.0.4 发布说明深度解析:poll 事件处理、QoS2 内存泄漏与客户端限速修复
Mosquitto 1.0.4 发布说明深度解析:poll 事件处理、QoS2 内存泄漏与客户端限速修复 导读 本文以 Eclipse Mosquitto 官方
后端消息队列消息路由
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考