这篇文章我压了很久没写。做物联网的应该都懂,MQTT 协议栈选型这件事,早几年根本不用纠结——Mosquitto 轻量、EMQX 功能全,开源免费还稳定,一个 broker 部署上去基本就不用管了。但这两年的实际情况是,越来越多的国内项目开始要求"国产化适配",企业采购审计、等保合规、供应链自主可控这些词频繁出现在需求文档里,技术群里隔三差五就有人在问:能不能用国产协议栈替代 Mosquitto / EMQX?替代以后开源版权和商用风险怎么算?
我恰好主导过一个工业物联网平台从 Mosquitto 迁移到国产自研协议栈的完整项目,从 2022 年开始调研,中间踩了无数坑,也把 Apache License、EPL、GPL 这些开源许可证的商用边界翻了个底朝天。这篇文章就把我们的调研结论、方案对比、迁移过程一次性讲清楚,给正在做同类选型的团队一个参考。
1. 为什么现在需要关注国产 MQTT 协议栈
先说一个容易被忽视的事实:MQTT 本身只是一个 OASIS 标准协议,不是某一家公司的私有技术。这意味着任何人都有权利基于公开规范实现自己的 broker、客户端、协议栈。所谓"国产 MQTT 协议栈",本质上不是要发明一个新协议,而是指由国内团队主导开发、代码可控、知识产权清晰的 MQTT 实现。
那为什么这两年这件事突然被提上议程?我总结下来主要有三个层面的驱动力。
第一是供应链合规压力。很多做政企、能源、智慧城市项目的公司,交付物里会被要求提供第三方组件清单,审计人员会逐项排查开源组件存在的合规风险。Mosquitto 和 EMQX 虽然是开源项目,但它们的开源许可证、版权方、社区治理模式各不相同,审计过程中需要逐条解释,工作量非常大。而采用国产自研方案,版权归属和开源合规问题就简单得多。
第二是服务连续性和技术自主的考量。之前圈子里有个很典型的案例:某个开源 broker 项目改了许可证,或者调整了社区版功能边界,直接导致依赖它的下游厂商被迫重新评估方案。这种不确定性对做长期项目、承诺了多年运维服务的团队来说,是无法接受的。自主可控意味着你随时有能力自己修 bug、自己加功能,而不是等上游社区施舍。
第三是功能定制和深度集成的需求。工业、车联网、能源场景里,标准 MQTT 往往不够用,需要扩展会话存储、设备鉴权、数据脱敏、国密加密等能力。用开源项目,这些改动要么被上游拒绝,要么因为协议栈太复杂改不动。自研协议栈反而能直接把业务逻辑内嵌进去。
不过这里要泼一盆冷水:如果只是做一个 Demo 项目、内部小规模使用,完全没有必要替换。Mosquitto 在低并发场景下极其稳定,EMQX 社区版在中小规模项目里也够用。替换的收益只在大规模、长期维护、合规要求高的项目中才能体现出来。
2. 开源许可证对比:Apache、EPL、GPL 到底差在哪
做协议栈替代,第一关就是搞清楚目标项目的开源许可证。很多人以为"开源 = 免费随便用",这是最大的误区。许可证本质上是一份合同,错了就是法律风险,不是简单的技术选型问题。
2.1 Mosquitto 的双许可机制
Mosquitto 是 Eclipse 基金会下的项目,使用 EPL 2.0 和 BSD 3-Clause 双许可。这里面的门道值得仔细说。
EPL 2.0 属于弱 Copyleft 许可证,它要求如果你修改了 Mosquitto 的源代码,并且将修改后的版本以某种形式分发出去,那么你修改的那部分代码必须以 EPL 2.0 协议开源。但如果只是把它作为独立进程运行,通过网络端口提供服务,不修改源码、不重新分发二进制,那 EPL 2.0 的传染性基本不会触发。
BSD 3-Clause 则是非常宽松的许可证,允许你自由使用、修改、分发,甚至闭源商用,唯一义务是保留版权声明和免责声明。Mosquitto 项目允许使用者在 EPL 和 BSD 之间二选一。这意味着如果你对 Mosquitto 内部协议栈做了深度定制,你完全可以选择以 BSD 条款来使用,前提是你不使用 EPL 许可证开源的代码部分——这个边界比较微妙,实践中最好的办法是只把 Mosquitto 当黑盒进程用,不做源码级修改。
我在实际项目中为规避边界模糊问题,选择的方式是"深度定制不做进程内修改"——通过插件机制和外部桥接来做扩展,而不是直接改 broker 的源码。
2.2 EMQX 的 Apache 2.0 与商业版边界
EMQX 是 EMQ 公司主导的 MQTT broker,开源社区版使用 Apache License 2.0。Apache 2.0 是目前最友好的商用许可证之一:允许自由使用、修改、分发,允许闭源商用,不要求修改后的代码开源,只要保留原始版权声明、修改声明和 NOTICE 文件。
听起来很美好,但实际商业使用中有一个几乎所有人都忽略的问题:你不能把 Apache 2.0 的授权范围延伸到 EMQX 所有版本上。EMQ 公司的主营业务是商业化企业版,企业版里包含集群编排、规则引擎、数据持久化、监控告警等一揽子高级功能,这些功能不在 Apache 2.0 的授权范围内。而且企业版和社区版的代码边界经常变动,一些在新版本社区版里的功能,到了下一个版本可能就被挪到企业版里了。
我调研时还发现一个更实际的问题:EMQX 社区版的升级节奏越来越慢,安全补丁也优先给企业版推送。如果你在一个需要做等保、渗透测试的项目里用社区版,漏洞修复完全取决于社区维护节奏,这可能成为审计中的短板。
2.3 强 Copyleft 协议避坑提醒
市面上还有一些 MQTT 协议栈使用 GPL 系列许可证。GPL 的传染性远强于 EPL,只要你把使用 GPL 代码的软件对外分发,整个衍生作品都必须以 GPL 开源。对于做闭源商业软件的公司来说,GPL 协议的项目基本不能碰。
识别方法很简单:看项目根目录的 LICENSE 文件,如果是 GPL、AGPL 开头的,直接放弃;如果项目仓库里没有明确的 LICENSE 文件,也不建议使用——代码可以随便看,但授权是未明确的,出事之后没有任何法律保障。
我个人的经验准则:商用场景下,许可证优先级排序是 Apache 2.0 > BSD/MIT > EPL > LGPL > GPL/AGPL。这不是说前面的协议完美无缺,而是责任边界相对清晰。
3. 主流替代方案盘点与选型分析
确定替换的决心后,摆在面前的有四条现实路线:采用国产开源 broker、基于成熟框架自研协议栈、使用国产物联网平台内置能力、采购商业版 MQTT 服务。每一路线的适用场景、技术门槛、长期维护成本都完全不同,我逐个拆解我们调研下来踩过的坑。
3.1 路线一:国产开源 Broker 直接替换
选择的切入点是 Gmqtt,Go 语言编写、Apache 2.0 许可证、同时支持 MQTT 3.1.1 和 5.0、在 GitHub 上活跃度不错的轻量级 broker 协议栈。国产属性明确,没有大基金会版权背景,代码量相比 Mosquitto 小很多。
我们做了两周的对比测试,一个小规模的压测结果是:单机 8 核 16G 的虚机环境,Gmqtt 可以稳定扛住 10 万连接、每秒 2 万条 QoS 1 消息。这个性能数据对大部分工业物联场景已经足够了。关键是它的代码结构非常干净,对 WebSocket、集群、插件机制都有预留接口,方便做功能扩展。
但要注意几个薄弱点:生态工具链比 Mosquitto/EMQX 少很多,比如 web 管理界面、规则引擎、数据桥接基本要自己写;野生 bug 比较多,需要团队本身对 MQTT 协议理解足够深,出了问题能自己查协议栈代码;社区答疑基本靠 GitHub issues,响应速度一般。
建议的定位是:中小规模(十万连接以内)、团队有 Go 后端能力、希望在 Broker 核心上做深度定制的项目,Gmqtt 是非常合适的基础底座。
3.2 路线二:基于 Netty / Java 生态自研协议栈
如果团队的技术栈偏 Java,而且对 MQTT 功能有非常明确的自定义需求,基于 Netty 自研一套精简 MQTT broker 是很多国产化项目的做法。我们最终其实也是走的这条路线,不过是基于另一个国内物联网框架的 MQTT 模块改的,后面细说。
自研 MQTT 编解码本身并不神秘,核心就是处理 CONNECT、PUBLISH、PUBACK、SUBSCRIBE、PINGREQ 这些报文类型。一个可用版本的最小实现包含以下内容:
- 解析 MQTT 固定头:报文类型、标志位、剩余长度;
- 处理可变头:协议名、协议级别、连接标志、保持连接、主题名、报文标识符;
- 处理 Payload:根据报文类型解析主题过滤器、QoS、消息体;
- 会话状态管理:维护客户端会话、主题订阅关系、离线消息队列;
- QoS 流程:QoS 1 的 PUBACK 应答,QoS 2 的 PUBREC/PUBREL/PUBCOMP 四次握手。
代码层面用 Netty 的 ByteBuf 和 ChannelHandler 可以很快搭出骨架。关键是几个容易被忽略的细节:剩余长度是变长编码,一个字节最高只能表示 127,大于这个数要用低 7 位循环编码;QoS 2 的报文去重要靠报文标识符做缓存,否则消息会重复;遗嘱消息的发布时机和 session 过期策略密切相关,处理不好会出现遗嘱误发。
自研最大的坑不是技术,而是后期维护。协议栈测试用例要覆盖异常输入、半包拆包、超大消息、恶意连接、畸形报文等,这些工作量比开发本身大得多。如果团队没有专职做网络协议的人,我强烈建议不要从零开始,而是找一个半成品框架做二次开发。
3.3 路线三:基于国产物联网平台的 MQTT 能力
这个思路往往被忽略,但实际落地效果反而最好。像 JetLinks、FastBee、IoTDC3 这类国产开源物联网平台,自带 MQTT broker 模块,而且默认实现了很多国内场景才需要的功能:统一的设备物模型、消息存储、规则引擎、HTTP API、用户权限体系。
我们最终就是选择了基于 JetLinks 的 MQTT 模块做替代。它底层基于 Netty 和 Reactor,协议栈本身是完整的 MQTT 3.1.1 实现,许可证是 Apache 2.0,项目活跃度高,国内社区资料很丰富。用它替换 Mosquitto 的工作量,主要是把原来直接用 MQTT topic 做设备数据上传的模式,改造成平台里"产品-设备-物模型-消息上报"的标准模式。
这么做的好处非常明显:你获得的不是一个孤零零的 broker,而是一个完整的数据接入平台。设备管理、在线状态、消息轨迹、数据转发都开箱即用,少了大量从零开发的工作。
缺点也很清晰:学习曲线陡峭、平台代码体量大、底层细节封装比较深。如果只是想把 Mosquitto 换成一个可控的 broker,用这套平台会有"杀鸡用牛刀"的感觉。
3.4 路线四:商业版 / 云托管 MQTT 服务
严格意义上这不是协议栈替代,而是服务替代。某些云厂商提供的 MQTT 实例服务,底层协议也是兼容 MQTT 3.1.1 和 MQTT 5.0 的,而且自带高可用、监控告警和国密支持。对不想自建基础设施、预算充足的团队,这是最省心的方式。
但这里有一个知识产权和合规上的关键差异需要注意:云托管服务的协议虽然是标准 MQTT,但接入层 SDK、设备影子、物模型映射通常都是闭源的,协议的自定义扩展点也有限。如果后期想加深度的协议定制,这种方案不具备可行性。
而且数据出境和数据主权问题在政企项目里非常敏感,使用公有云的托管服务前,一定要确认数据链路是否完全在合规区域内部流转。
4. 开源版权与商用风险:必须避开的五个坑
在最终做决定前,有几个版权层面的坑是真实项目中反复出现的,这里单独拉出来讲。
4.1 只读 LICENSE 文件,不看贡献者协议的坑
很多开源项目仓库里的 LICENSE 文件是 MIT 或 Apache 2.0,看起来人畜无害。但这个文件只能约束项目主仓库的代码,如果项目接收了来自企业贡献者或个人的代码提交,那些代码块可能在 README 里额外标注了版权归属,甚至附加了不同的授权条款。必须要看项目中是否存在 CONTRIBUTORS、AUTHORS、COPYRIGHT 这类文件,以及代码文件头部的版权注释。如果发现版权信息混乱的项目,建议直接弃用。
4.2 以"学习参考"为名直接搬代码的坑
这是一个非常有争议但实际高频发生的行为:团队为了节省时间,参考某个 GPL 协议的 MQTT 协议栈源码,自己用另一种语言重写了一遍。表面上"抄逻辑不抄代码",但代码结构和接口设计高度相似的话,衍生作品的认定仍然可能成立。之前圈子里曾有协议栈抄 EPL 项目代码被原项目方发函要求下线的案例。正确做法是保留设计思路,重构代码模块划分,最后做代码相似度自检。
4.3 忽视"分发"场景的坑
很多人以为开源许可证只要"自用不对外"就没事,实际上"分发"这个概念在软件行业里指的不只是卖软件,还包括:给客户部署的硬件里预装软件、向上级单位交付包含该组件的系统、把构建产物发布到公共仓库。只要这些行为发生了,开源许可证的各项义务就会被激活。我见过一个项目组把应用打包到工控机里交付给工厂,里面有 GPL 组件却完全没有开源配套,最后被甲方安全团队审计出来要求整改。
4.4 商标和品牌滥用的坑
MOSQUITTO、EMQX 都是注册商标。即使你完全基于 Apache 2.0 的 EMQX 源码二次开发,也不能在产品名称、官网文案、宣传材料里直接使用 EMQX 这个名字。审计中发现过有项目在给甲方的技术方案里写"基于 EMQX 深度定制",结果 EMQ 公司的法务函直接发到了甲方那里。正确说法是"兼容 MQTT 规范的自主 broker 实现"。
4.5 依赖组件版本滞后的坑
即便你只选了 Apache 2.0 协议的项目,这个项目自身也可能依赖了其他 GPL 协议的组件。以 broker 为例,常见的坑包括:依赖的加密库是 OpenSSL 的旧版本(OpenSSL 1.1.1 及以下是 Apache 2.0,但更早版本是双许可)、依赖的构建工具链带了 GPL 插件、打包时引入了 LGPL 的静态链接库。做一个依赖树级扫描比只看主 LICENSE 文件重要得多。
5. 落地案例:基于国产平台替换 Mosquitto 的完整过程
接下来把我们项目实际执行替换的过程完整复盘一遍。先交代背景:我们原来的架构里,Mosquitto 作为设备接入层 broker,部署在 3 台虚机上,通过桥接模式做了简单的负载分担。设备侧是各类 Modbus RTU 采集网关,通过 4G 模块把 JSON 数据包以 QoS 1 的方式发布到 MQTT 主题下。后端有一个专门的消费者服务订阅这些主题,把数据清洗后写入时序数据库。
这个架构在 1 万设备以内很稳,但问题出在两个地方:一是 Mosquitto 没有内置的鉴权管理界面,我们在外面做了一层 HTTP 鉴权插件,登录态经常因为 Redis 故障导致大批设备掉线;二是项目验收审计时要求提供全部第三方组件的许可证材料,Mosquitto 的 EPL/BSD 双许可解释成本太高。我们决定用 JetLinks 的 MQTT 模块替换掉中间的 Mosquitto 部分。
迁移过程大概分四步。
第一步是协议兼容性验证。我们梳理了原来设备上报的所有主题和消息格式,对比 MQTT 报文格式差异。JetLinks 默认的 MQTT 接入协议设计得比较简单——设备上报消息统一发到一个特定主题下,平台通过消息体里的 productKey、deviceName 字段识别设备和数据类型。好消息是它也提供自定义消息解析接口,可以按照原有主题结构来解析。这里花了两周时间做适配,包括设备端消息确认机制、QoS 1 下行消息的缓存与重发逻辑。
第二步是设备接入层切换。JetLinks 的网络组件里内置了完整的 MQTT 编解码器和 TCP 服务,不需要自己写底层网络代码。我将原 Mosquitto 启动参数中的 listener、allow_anonymous、password_file 等配置含义逐项映射到 JetLinks 的设备接入参数上。有一个细节要注意:Mosquitto 允许同一个 clientId 重复连接时踢掉旧连接,JetLinks 默认也这么做,但不会发送 DISCONNECT 报文给旧连接端,设备端如果依赖"被踢下线时收到断连原因"来判断重连策略,需要额外的设计。
第三步是后端消费者改造。原来的 Kafka 流程不变,但订阅方式从直接订阅 MQTT 主题改成了从 JetLinks 的规则引擎里配置消息转发。规则引擎可以把收到的设备消息直接转发出 HTTP 接口、Kafka、RocketMQ、RabbitMQ 等。为了最小化后端改动,我们把规则引擎的 Kafka 转发目标指向了原来消费者监听的同一个 topic,数据结构格式保持一致。这一步的核心是搞清楚 JetLinks 的物模型消息数据结构,它会在原始消息外面包一层信封,包含 deviceName、productKey、messageType 等字段,需要在规则引擎的"消息格式"里做字段透传或格式转换。
第四步是灰度迁移。我们没有做"新老双跑",因为设备端 4G 模块的双连接支持不好。我们的做法是分区域切换:华东区域设备先指向新 broker,观察一周运行情况,再切华北、华南,最后把 Mosquitto 彻底下线。切换时遇到的最大问题在后面第 6 节里展开。
整个迁移周期,包括前期调研、代码适配、灰度切换,一共花了 8 周。开发量主要在规则引擎的字段映射和设备接入的自定义解析上,约 1500 行 Java 代码,团队投入是 2 个后端、1 个测试、1 个运维。
6. 常见问题与排查技巧实录
6.1 设备频繁掉线重连,日志显示 CONNACK Code 5
这个错误码是"连接已授权"但在使用替换 broker 后设备端出现掉线,大概率是 broker 侧的会话保存策略变了。Mosquitto 对 session 过期时间的默认行为是持久会话永久保存,而国产平台默认可能把 session 过期时间配置得比较短。设备端的维持连接 KeepAlive 如果设置得比 broker 端会话过期时间更长,就会造成 broker 主动断开连接。
排查时要先看 broker 日志中是否出现"session expired"相关记录,再检查接入层的会话过期参数。在我们的案例里,JetLinks 是基于内存保存会话状态的,设备断网超过 60 秒后 session 会被清理,导致大量 4G 模块重新建立连接后又收不到离线期间的下行数据。
解决办法是在消息下行时做一次"是否在线"状态判断,离线消息进入延迟队列,等待设备上线后再下发,而不是依赖 broker 的离线消息缓存。
6.2 MQTT 5.0 属性字段全部丢失
新 broker 如果只实现了 MQTT 3.1.1,而原系统使用 MQTT 5.0 的消息过期时间、主题别名、内容类型等属性,这些字段会全部被吞掉。排查方式是用一个支持打印完整报文的调试客户端(比如 paho 开启调试日志)分别连接新旧 broker,把 CONNACK、PUBLISH、SUBACK 报文的 hex dump 逐一对比。
这里要提醒的是,不要看客户端 SDK 的抽象层返回结果,很多 SDK 在看到 MQTT 5.0 属性时不报错,只是静默丢弃。我们当时排查了很久才发现是设备端某款 4G 模块固件默认开启了 MQTT 5.0,新平台不支持协商降级,导致 topic 里的消息全部丢失。最后的解决办法是让设备端强制使用 MQTT 3.1.1 接入。
6.3 100% 内存占用与连接数不匹配
替换 Mosquitto 后,某台节点内存持续飙升但连接数并没有明显增长,最终 OOM。定位时用 jmap dump 分析发现,堆里有大量 MqttPublishMessage 对象未被释放,这些消息的主题全部集中在几个高频数据主题上。
原因是新平台的 broker 会默认把 QoS 1 消息保留在内存中,直到收到客户端的 PUBACK 确认。如果客户端是异步消费模式,消费速率跟不上生产速率,大量未确认消息就会堆积在 broker 内存中。解决思路有两个方向:一是调整客户端的预取数量和手动 ack 策略,二是在规则引擎层面做消息丢弃策略,对超时未确认的消息优先释放内存。
6.4 WebSocket 客户端无法接入
原来通过 MQTT over WebSocket 连接的浏览器客户端,在切换后连接一直失败。查了接入配置才发现,国产平台的 WebSocket 监听端口和 TCP 端口是分开配置的,而且默认可能关闭。在配置文件里启用 ws 监听后,还要注意原客户端连接路径的问题——Mosquitto 默认 WebSocket 路径是 /mqtt,而 JetLinks 默认路径是 /mqtt 或根路径,需要确认配置和前端连接代码保持一致。
6.5 批量设备认证性能瓶颈
替换后出现了设备批量上线时认证请求堆积的问题。Mosquitto 的认证模式是同步阻塞类型,而国产平台默认接入了数据库查询做设备鉴权,批量上线瞬间产生大量数据库连接和慢查询,导致认证超时。
优化方式是对设备鉴权接口增加本地缓存,设备上线后把认证结果缓存在 Redis 里,设置 5 分钟过期时间。同时把鉴权接口改成异步批量判断,而不是一个设备一次查询。这个优化完成后,一台 16 核 32G 的节点扛住了 3 万台设备同时上线的场景。
7. 最后再分享一点实操心得
经过这轮完整替换,我感触最深的一点是:替代 Mosquitto / EMQX 的技术难度,远小于替代过程中暴露出来的流程复杂度和组织协作成本。
协议栈本身只是个工具,真正决定项目成败的是团队对 MQTT 协议本身的理解深度。如果你不清楚 QoS 1 和 QoS 2 在不同断线场景下的行为差异、不理解 session 接管和遗嘱消息的触发时机、不知道 MQTT 5.0 新增了哪些属性字段,那么不管是国产协议栈还是开源 broker,出了问题都一样抓瞎。
选型上我给团队的建议一直很朴素:能买到商业服务并接受闭源约束的,直接买服务;必须自建而且有合规要求的,优先选 Apache 2.0 且社区活跃的国产项目;只有功能需求非常独特、团队又有协议栈开发能力时,才考虑从零自研。不要为了"国产化"而国产化,替代的最终目的是让自己的系统更可控、更合规、更好维护。
最后再分享一个容易忽略的细节:License 审查和依赖树扫描应该做成 CI 流程的一部分,而不是在交付前手工做一次。我们后来引入了开源组件扫描工具,每次构建自动生成 SBOM(软件物料清单),对接交付物清单,省了很多审计沟通的时间。这比任何协议栈选型都重要。