mqtt-plus 架构解析(六):多 Broker 管理,如何让一个应用同时连接多个 MQTT 服务

mqtt-plus 架构解析(六):多 Broker 管理,如何让一个应用同时连接多个 MQTT 服务

摘要

很多 MQTT 项目真正进入生产后,都会碰到一个问题:应用往往不是只连一个 broker,而是同时面对云端、本地边缘、测试环境甚至不同协议接入层。mqtt-plus 当前的做法,不是把多 broker 做成多套系统拼接,而是让每个 broker 拥有各自独立的 adapter 和连接生命周期,同时让路由、监听注册、消息发布接口继续共享同一套核心模型。本文会结合 MqttBrokerDefinitionMqttClientAdapterFactoryRegistryMqttBrokerAutoConfigurationMqttClientAdapterRegistryDefaultMqttTemplate,拆解这套结构为什么成立。

项目地址

项目地址:

https://github.com/mqttplus/mqtt-plus

配套的示例工程:

https://github.com/mqttplus/mqtt-plus-examples

如果你对这个方向感兴趣,欢迎关注、试用,也欢迎一起交流 issue 和 PR。

如果这篇文章对你有帮助,欢迎点赞、收藏,也欢迎给项目一个 Star。


到了第 6 篇,系列要回答的问题已经不再是"单条消息怎么走",而是更偏系统结构的一层:

  • 如果应用里配置了多个 broker,会不会变成多套逻辑混在一起?
  • 如果每个 broker 都独立,那为什么又能共享同一个 MqttTemplate、同一个 router、同一批 listener 定义?
  • 多 broker 到底是"多个系统拼接",还是"一套核心 + 多个连接实例"?

mqtt-plus 当前给出的答案非常明确:

broker 隔离发生在 adapter 和连接生命周期层;共享发生在 core 里的路由、注册、发布抽象和 Spring 装配层。

一、这篇文章到底想回答什么?

这一篇只回答三个问题:

  • broker 的独立性到底落在什么对象上
  • 多个 broker 为什么还能共享同一套核心能力
  • starter 是如何把 YAML 里的多个 broker 配置转成多个真实 adapter 实例的

如果只记住一句话,那就是:

mqtt-plus 的多 broker 设计,不是复制多套框架实例,而是让"每个 broker 一条连接实例链路",同时让上层路由、监听和发布模型保持统一。

二、先看多 broker 的整体结构

先不要急着看配置和工厂,先看整体关系。
Broker A
Adapter A
Broker B
Adapter B
Shared Router
Shared Listener Registry
Shared Listener Invoker
DefaultMqttTemplate
MqttClientAdapterRegistry

这张图里最关键的不是"有两个 broker",而是这两个事实同时成立:

  • 每个 broker 对应自己的 adapter 实例
  • adapter 的上层能力并没有跟着复制两份,而是共享 routerregistrytemplate

换句话说,mqtt-plus 的多 broker 不是"多套框架并存",而是:

  • 连接层按 broker 隔离
  • 核心模型按框架共享

这也是为什么它在结构上比较轻,而不是不断复制配置和 Bean。

三、broker 的独立性首先落在 MqttBrokerDefinition

每个 broker 在 core 里都有一个明确的定义对象:MqttBrokerDefinition

它至少承载了这些信息:

  • brokerId
  • host / port
  • clientId
  • username / password
  • cleanSession
  • sslEnabled
  • keepAliveInterval
  • connectionTimeout
  • inboundThreadPool

这一层非常重要,因为它说明 mqtt-plus 不是简单拿一个字符串标记 broker,而是把 broker 看成一组完整的连接定义。

这也意味着 broker 的独立性不是停留在"名字不一样",而是:

  • 连接参数可以不同
  • 会话策略可以不同
  • 入站线程池也可以不同

特别是 inboundThreadPool 这一点,它说明"多 broker"不只是"多地址",还包括每个 broker 在消息接入吞吐上的独立运行参数。

设计决策: mqtt-plus 没有把 broker 仅仅建模成一个 brokerId -> host:port 的轻量映射,而是使用 MqttBrokerDefinition 承载完整连接定义。这样做的重点,是让"多 broker"真正拥有独立运行属性,而不是只有一个逻辑标签。

四、FactoryRegistry 为什么是多 broker 的关键枢纽?

有了 MqttBrokerDefinition 之后,下一个问题就是:

这个 broker 应该用哪个 adapter 去创建?

这一层由 starter 里的 MqttClientAdapterFactoryRegistry 负责。

它做的事情很集中:

  • 收集所有 MqttClientAdapterFactory
  • adapterId 建立映射
  • 支持显式按 adapter 选择
  • 在没有显式指定时,按 mqtt 版本和 classpath 条件自动挑选兼容实现

当前真实实现里,它有一个特别值得注意的策略:

  • 如果没有显式指定 adapter
  • 且 classpath 中存在 spring-integration
  • 且版本兼容
  • 那它会优先选择 spring-integration

也就是说,多 broker 不是"一股脑全走同一个硬编码实现",而是每个 broker 在创建时都经过一次工厂解析。
Yes
No
Yes
No
Broker Properties
explicit adapter?
getRequiredByAdapterId(adapterId)
spring-integration available and compatible?
choose spring-integration factory
scan factories by mqttVersion
create adapter instance

这张图说明了一件很重要的事:

broker 独立,不代表配置层要重复写很多分支逻辑;adapter 选择逻辑被统一收敛进了 factory registry。

这也是第 6 篇最核心的设计点之一。

五、多个 adapter 是怎么注册起来的?

把配置真正变成多个运行中的 adapter,发生在 MqttBrokerAutoConfiguration.registerAdapters(...) 里。

它的主路径非常清楚:

  1. 遍历 mqtt-plus.brokers
  2. 取出每个 broker 的 BrokerProperties
  3. 调用 toDefinition(brokerId) 构造 MqttBrokerDefinition
  4. 通过 MqttClientAdapterFactoryRegistry.resolveFactory(...) 选出 factory
  5. 调用 factory.create(definition, inboundMessageSink) 创建 adapter
  6. 把所有 MqttConnectionListener 挂到 adapter 上
  7. 注册到 MqttClientAdapterRegistry
  8. 调用 adapter.connect() 建立连接

这里最值得注意的是第 7 和第 8 步之间的关系。

starter 不是"连上以后再注册",而是:

  • 先注册 adapter
  • 再 connect
  • 如果 connect 失败,再从 registry 里移除

对应测试也验证了这点:

  • 连接成功时,adapter 已注册且可查找
  • 连接失败时,adapter 会从 registry 移除,不留下脏状态

这意味着多 broker 管理不仅有创建逻辑,也有最基本的一致性清理逻辑。

设计决策: MqttBrokerAutoConfiguration 先注册 adapter、再 connect、失败后回滚 registry。这种顺序让 starter 层既保留了统一注册入口,又避免在连接失败时留下一个"可查询但不可用"的 broker 适配器。

六、为什么说 broker 隔离和核心共享是同时成立的?

这一点如果只看代码,很容易觉得"既然有多个 adapter,那是不是路由也要有多份"?

答案是否定的。

核心共享主要体现在三处:

1. 入站消息仍然走同一个 router

每个 adapter 创建时,都会拿到同一个 MqttInboundMessageSink

也就是说,虽然 adapter 有多个,但它们在把消息交回框架时,仍然会回到同一个入口:

  • adapter 自己负责连接 broker
  • adapter 收到消息后交给统一 sink
  • sink 再进入统一 router

这就是"连接独立、消息主链共享"。

2. listener 注册表仍然是一份

MqttListenerRegistry 还是全局共享的一份注册表。

它并不是"每个 broker 一份 registry",而是用 brokerId + topic 去做 resolve。

这意味着:

  • listener 模型是统一的
  • 匹配时才根据 brokerId 做隔离
  • 隔离发生在解析条件,而不是 Bean 复制层

3. 发布接口仍然是一个 MqttTemplate

DefaultMqttTemplate 的做法也很直接:

  • 调用方传入 brokerId
  • template 从 MqttClientAdapterRegistry 里查到对应 adapter
  • 再把真正的 publish 委托给那个 adapter

所以从业务视角看,多 broker 并不会把 API 形状搞复杂。你仍然是:

  • 一个 MqttTemplate
  • 多个 brokerId
  • 一套统一 publish API

这就是 mqtt-plus 多 broker 设计里最实用的一点:

对框架内部来说,broker 是多个连接实例;对业务接口来说,broker 只是一个显式参数。

七、这种设计给业务带来了什么实际好处?

如果把第 6 篇放回真实项目场景里看,这套结构至少有 4 个直接收益。

1. 多环境或多层级接入不需要复制框架能力

比如:

  • cloud 负责云端设备接入
  • edge 负责本地边缘消息
  • test 负责测试 broker

你不需要为每个 broker 再搭一套独立消息框架,只需要新增 broker 配置和对应 adapter 实例。

2. 路由、监听、发布模型保持一致

多 broker 场景最容易把代码写乱的地方,是:

  • A broker 一套 API
  • B broker 另一套 API
  • listener 和 publisher 都要按 broker 分叉组织

mqtt-plus 现在避免了这个问题。核心 API 形状没变,只是 brokerId 成了显式维度。

3. adapter 可以按 broker 选择实现

当前 factory registry 已经支持:

  • 显式选 adapter
  • 默认按兼容性自动选择

这意味着未来不是所有 broker 都必须绑定同一个 transport 实现。

4. 连接失败不会污染全局 registry

starter 在 connect() 失败时会做回滚,这对多 broker 场景尤其重要。

因为一旦其中一个 broker 连不上,不应该让 registry 留下一个"看起来存在、实际上不可用"的 adapter。

八、这套多 broker 设计当前的边界在哪里?

和前面几篇一样,第 6 篇也要把边界说清楚。

当前 mqtt-plus 的多 broker 模型已经非常清楚,但它并没有继续往更复杂的 broker 编排能力上扩展,比如:

  • 没有 broker 间的自动 failover 抽象
  • 没有跨 broker 的发布策略路由
  • 没有"一个主题自动镜像到多个 broker"的高级协调层

现在它解决的问题更明确:

  • 让一个应用可以同时持有多个 MQTT 连接实例
  • 保证这些连接在 adapter 层彼此独立
  • 保证它们在 core 层继续共享统一抽象

所以它更像一个"清晰的多连接基础架构",而不是一个"多 broker 编排平台"。

这个边界其实是合理的,因为只要先把 broker 维度的隔离和共享建模清楚,后面更复杂的协调能力才有稳定落点。

九、小结

第 6 篇最重要的结论,其实可以压缩成两句话:

  • 每个 broker 都有自己独立的 MqttBrokerDefinition、adapter 实例和连接生命周期
  • 所有 broker 又共享同一套 router、listener registry、template 和 Spring 装配模型

也正因为这种"下层隔离、上层共享"的结构成立,mqtt-plus 才能在不把 API 搞复杂的前提下,支持一个应用同时连接多个 MQTT 服务。

如果说前几篇更多是在讲"单条消息怎么流",那第 6 篇回答的是另一件事:

当系统边界扩大到多个 broker 时,框架如何保持结构稳定。

下一篇会继续顺着这个方向往前走,进入一个更偏运行态的问题:为什么首次连接、动态订阅变更和重连恢复,最终可以收敛到同一条协调路径。

系列导航

本文是 mqtt-plus 架构解析 系列的第 6/10 篇。

# 主题 链接
1 总览:分层架构与设计哲学 链接
2 消息路由:一条 MQTT 消息如何到达你的 @MqttListener 链接
3 Payload 序列化与反序列化:双链设计的取舍 链接
4 拦截器链:MqttMessageInterceptor 的扩展点设计 链接
5 错误处理:ErrorAction 聚合策略的设计逻辑 链接
6 多 Broker 管理:如何让一个应用同时连接多个 MQTT 服务 本文
7 动态订阅与重连恢复:Reconciler 的协调机制 链接
8 Spring Boot 自动装配:零件是怎么被粘合起来的 链接
9 测试体系:MqttTestTemplateEmbeddedBroker 的设计 链接
10 从内部项目到开源框架:mqtt-plus 的抽取过程与决策 链接

上一篇:错误处理:ErrorAction 聚合策略的设计逻辑

下一篇:动态订阅与重连恢复:Reconciler 的协调机制

相关推荐
不懂的浪漫2 小时前
mqtt-plus 架构解析(十):从内部项目到开源框架,mqtt-plus 的抽取过程与决策
spring boot·mqtt·架构·开源
CoovallyAIHub6 小时前
视频理解新范式:Agent不再被动看视频,LensWalk让它自己决定看哪里
算法·架构·github
CoovallyAIHub6 小时前
斯坦福丨AirVLA:将地面机械臂模型迁移至无人机实现空中抓取,成功率从23%提升至50%
算法·架构·github
一 乐6 小时前
酒店预订|基于springboot + vue酒店预订系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·酒店预订系统
竹之却7 小时前
【Agent-阿程】OpenClaw智能体架构深度解析与实战应用
架构·大模型应用·ai框架·openclaw
qq_454245037 小时前
通用引用管理框架
数据结构·架构·c#
独特的螺狮粉7 小时前
云隙一言:鸿蒙Flutter框架 实现的随机名言应用
开发语言·flutter·华为·架构·开源·harmonyos
格鸰爱童话7 小时前
向AI学习项目技能(六)
java·人工智能·spring boot·python·学习
heimeiyingwang7 小时前
【架构实战】SQL调优实战:从执行计划到索引优化
数据库·sql·架构