Mosquitto 1.0.2 版本解析:$SYS 持久化缺陷修复与配套工具链改进
2026/9/24 0:10:30 网站建设 项目流程
  • 后端
  • 消息队列
  • 消息路由

【免费下载链接】mosquitto

Eclipse Mosquitto - An open source MQTT broker

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

本篇技术指南围绕 Eclipse Mosquitto 1.0.2 版本发布公告展开,逐一拆解本次 bugfix 版本修复的五大问题:持久化场景下$SYS/#订阅导致数据库缺失消息、部分系统上的线程问题、SSL 测试用例的 socket 泄漏、CMake 安装缺失pskfile.example示例文件,以及db_dump参数打印异常。读完本文,你将理解 Mosquitto 持久化机制中$SYS主题消息的特殊处理逻辑,并掌握db_dump工具的使用方法与输出格式。

版本概览:一次聚焦稳定性与工程细节的 bugfix 发布

Mosquitto 1.0.2 发布于 2012 年 8 月 19 日(发布公告见 www/posts/2012/08/version-1-0-2-released.md),性质是一次典型的 bugfix release——不引入新功能,而是集中修复 broker、客户端库、测试脚本与构建脚本中的缺陷。发布公告按Broker、Library、Tests、Build scripts、Other五个类别组织变更内容,这种分类方式在仓库根目录的 ChangeLog.txt 中得到完整对应,其中 1.0.2 条目(标注日期 20120919)与公告逐条一致,可用于交叉核对。

值得说明的是:当前仓库已迭代至更晚版本(持久化数据库格式已升级为 v6,见 src/persist.h),因此下文涉及的源码均以当前仓库实现为准,用于阐释这些修复背后的设计意图与机制。

Broker 修复:$SYS/# 订阅导致持久化数据库消息缺失

本次修复中最具技术含量的是 Broker 问题:

如果 broker 开启了持久化(persistence),一个持久客户端(durable client)订阅了$SYS/#主题,并且在其消息队列中还有消息时重启 broker,则持久化数据库会出现消息缺失,导致 broker 无法正常重启。

问题根因:$SYS 消息的特殊持久化策略

要理解这个 bug,需要先了解 Mosquitto 对$SYS主题消息的特殊处理。$SYS主题树是 broker 内部自生成的系统状态主题(如$SYS/broker/uptime$SYS/broker/connection/+/state等),其产生与持久化逻辑都不同于普通业务消息。

当前仓库的 src/persist_write.c 中,persist__message_store_save()函数揭示了这套策略的核心规则:

if(!strncmp(base_msg->data.topic, "$SYS", 4)){ if(base_msg->ref_count <= 1 && base_msg->dest_id_count == 0){ /* $SYS messages that are only retained shouldn't be persisted. */ continue; } /* Don't save $SYS messages as retained otherwise they can give * misleading information when reloaded. They should still be saved * because a disconnected durable client may have them in their * queue. */ chunk.F.retain = 0; }

这段代码体现了三个关键设计决策:

  1. 纯 retained 的$SYS消息不写入持久化数据库——$SYS主题是 broker 运行时状态,重启后会重新生成,将其固化为 retained 消息反而会给出误导性信息;
  2. 但被持久客户端队列引用的$SYS消息必须保存——因为这些消息是投递给离线持久客户端的承诺,丢了就无法恢复;
  3. 保存时强制清除 retain 标记,避免重启后$SYS消息以 retained 身份残留。

同理,在persist__client_messages_save()中(src/persist_write.c),写客户端队列消息时会再次检查:

if(!strncmp(cmsg->base_msg->data.topic, "$SYS", 4) && cmsg->base_msg->ref_count <= 1 && cmsg->base_msg->dest_id_count == 0){ /* This $SYS message won't have been persisted, so we can't persist * this client message. */ cmsg = cmsg->next; continue; }

1.0.2 修复的意义

1.0.2 修复的正是上述两处检查在旧版本中的不一致:当持久客户端同时满足"订阅$SYS/#"+"队列中有$SYS消息"+"broker 重启"三个条件时,旧逻辑会把队列中的$SYS消息与 base message 存储一并漏写,导致持久化数据库中的消息引用(store_id)悬空,broker 重启解析数据库时无法正确恢复客户端队列,最终表现为"无法正常重启"。

从当前实现回看,正确做法是:只有"无队列引用且仅 retained"的$SYS消息才被排除,凡是 durable client 队列仍引用的$SYS消息都必须随数据库落盘。这要求消息存储(DB_CHUNK_BASE_MSG)与客户端消息(DB_CHUNK_CLIENT_MSG)两类 chunk 的写入逻辑保持一致的引用计数判断,任何一处的遗漏都会造成数据不一致。

配置触发条件

该 bug 的复现需要持久化功能开启,相关配置项在 src/conf.c 中解析:

  • persistence true:开启 broker 持久化;
  • persistence_file:持久化数据库文件名(默认mosquitto.db);
  • persistence_location:持久化文件所在目录;
  • autosave_interval:自动保存间隔,默认 1800(秒),见 src/conf.c;
  • persistent_client_expiration:持久客户端会话过期时长,默认 0(不过期)。

其中autosave_interval的触发逻辑在 src/loop.c:当persistence开启且autosave_interval非零时,主循环会累计persistence_changes,达到阈值或超过时间间隔即触发数据库落盘。

Library 修复:部分系统上的线程问题

发布公告中 Library 部分仅一条:"修复某些系统上的线程问题。"虽然公告未给出细节,但从 Mosquitto 客户端库的架构可以推断:libmosquitto 同时支持多线程回环(mosquitto_loop_start())与单线程回环(mosquitto_loop())两种模式,二者会共享网络与消息队列结构,任何互斥锁初始化顺序、临界区保护或条件变量使用不当,都会在特定平台(尤其是线程调度行为不同的系统)上表现出偶发崩溃或数据竞争。

Tests 修复:SSL 测试后的 socket 关闭

1.0.2 修复了08-ssl-connect-no-auth-wrong-ca.py测试结束后未关闭 socket 的问题——该测试验证"使用错误 CA 的 SSL 连接应当被拒绝",若不关闭 socket,泄漏的文件描述符会让后续测试用例出现干扰。

当前仓库中该测试仍存在,见 test/broker/08-ssl-connect-no-auth-wrong-ca.py。测试逻辑为:启动配置了 CA 与证书的 broker,客户端使用另一套 CA(test-alt-ca.crt)发起 SSL 连接,预期握手抛出ssl.SSLError,并在finally块中确保ssock.close()

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=f"{ssl_dir}/test-alt-ca.crt") context.minimum_version = ssl.TLSVersion.TLSv1_2 ssock = context.wrap_socket(sock, server_hostname="localhost") ssock.settimeout(20) try: ssock.connect(("localhost", port1)) except ssl.SSLError as err: if err.errno == 1: pass finally: ssock.close()

该用例注册在 test/broker/test.py 的测试清单中,作为 broker SSL 连接测试序列的一环。try/except/finally结构正是 1.0.2 修复目标——无论握手成功与否,socket 都必须被显式关闭,避免影响后续测试。

Build scripts 修复:CMake 安装 pskfile.example

1.0.2 修复了 CMake 构建体系未安装pskfile.example的问题(对应 bug #1037504)。此前只有 Makefile 构建体系在make install时安装该文件,使用 CMake 构建的用户安装后找不到 PSK 示例配置文件。

当前仓库中,两种构建体系均已覆盖该文件的安装:

  • CMake:CMakeLists.txt 中custom_install(FILES aclfile.example pskfile.example pwfile.example DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/mosquitto")
  • Makefile:Makefile 中$(INSTALL) -m 644 pskfile.example "${DESTDIR}/etc/mosquitto/pskfile.example"

pskfile.example的内容非常简单,演示了 TLS-PSK(预共享密钥)认证的明文密钥文件格式——每行一条身份:密钥记录:

id:deadbeef easy:12345

该文件配合 broker 配置中的psk_file选项使用,用于为使用 PSK 的客户端提供预共享密钥。

Other 修复:db_dump 参数打印 message store 与 sub chunks

发布公告最后一条修复了db_dump工具的参数打印问题——具体是打印 message store(base message)与订阅(subscription)chunk 时的参数传递错误,导致输出信息不正确。

db_dump是 Mosquitto 附带的持久化数据库离线查看工具,源码位于 apps/db_dump/db_dump.c。其用法(apps/db_dump/db_dump.c):

Usage: db_dump [--stats | --client-stats | --json] <mosquitto db filename>
  • 直接指定数据库文件:逐 chunk 打印全部内容;
  • --stats:仅统计各类 chunk 的数量(CFG / BASE_MSG / CLIENT_MSG / RETAIN / SUB / CLIENT);
  • --client-stats:按客户端统计订阅数与队列消息数;
  • --json:以 JSON 形式输出。

持久化数据库由 15 字节魔数(magic)+ CRC + 版本号 + 若干 chunk 组成,chunk 类型定义于 src/persist.h:

DB_CHUNK_CFG 1 DB_CHUNK_BASE_MSG 2 DB_CHUNK_CLIENT_MSG 3 DB_CHUNK_RETAIN 4 DB_CHUNK_SUB 5 DB_CHUNK_CLIENT 6

db_dump的主循环即按这些类型分发处理(apps/db_dump/db_dump.c),遇到未知 chunk 类型会跳过并给出警告。

1.0.2 修复所涉及的输出逻辑位于 apps/db_dump/print.c:

  • print__base_msg()(apps/db_dump/print.c)打印 Store ID、源端口、源 MID、主题、QoS、Retain、Payload 长度、过期时间,并对小于 256 字节且通过 UTF-8 校验的 payload 做文本输出,随后打印 MQTT v5 属性(如 payload format、content type、user property 等);
  • print__sub()(apps/db_dump/print.c)打印客户端 ID、订阅主题、QoS、订阅标识符与订阅选项。

正确传入 chunk 结构指针(而非其他类型)是这两处输出准确性的前提,正是 1.0.2 修复的参数问题。这些示例输出也可作为运维排查"持久化数据库内容是否正确"的对照依据。

总结与验证建议

Mosquitto 1.0.2 虽是一次小版本 bugfix 发布,但其修复内容覆盖了从核心 broker 持久化一致性、客户端库线程安全,到测试脚本资源管理、构建安装完整性与工具输出正确性的全链路,体现了开源 MQTT broker 工程化维护的细致程度。

如果你想在本地验证本文提到的机制,可以按以下路径操作:

  1. 核对版本记录:阅读 ChangeLog.txt 中 1.0.2 条目,与发布公告逐条比对;
  2. 理解 $SYS 持久化策略:阅读 src/persist_write.c,重点观察$SYS前缀判断与ref_count/dest_id_count的配合;
  3. 演练 db_dump:使用persistence true配置运行 broker 一段时间后停止,对生成的mosquitto.db执行db_dump --stats <db文件>db_dump <db文件>,观察各类 chunk 的输出结构(当前版本数据库格式为 v6);
  4. 查看 PSK 示例:参考仓库根目录的 pskfile.example 与aclfile.examplepwfile.example三个示例文件,理解 broker 配置文件中对应选项的输入格式。

需要特别提醒的是,$SYS主题消息的持久化属于 broker 内部机制,1.0.2 之后的版本在此基础上持续演进,当前仓库的 src/persist_write.c 已包含 MQTT v5 属性与消息过期时间等新逻辑,但其"纯 retained 的 $SYS 消息不落盘、被持久客户端队列引用的必须落盘且清除 retain"的核心原则一脉相承。

  • 后端
  • 消息队列
  • 消息路由

【免费下载链接】mosquitto

Eclipse Mosquitto - An open source MQTT broker

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

相关推荐

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询