一条MQTT消息在EMQX内部怎么流转:订阅树、主题匹配、会话队列(架构视角)
前面几篇我们都在"Broker 外面"看 MQTT。这一篇钻进去:一条 PUBLISH 消息从设备发出来,在 EMQX 内部到底经历了哪些关卡,才最终到达每一个订阅者?看懂这条链路,你就不再是"会配会用的使用者",而是"能定位问题在哪个环节"的架构者。本文以 EMQX 5.x 的分层架构为例,思路对其它 Broker(Mosquitto、HiveMQ)同样适用。
一、先看 EMQX 的分层架构
EMQX 在设计上把职责分成清晰的几层(每层专注一件事):
记住这四层的分工,整条链路就清晰了:
- 连接层:只负责"把字节收进来、把字节发出去",不关心消息内容。
- 会话层:为每个客户端维护一个会话——它订阅了什么、哪些 QoS1/2 消息在等确认、离线时有哪些消息在排队(对应第 8 篇的"会话保持")。
- 路由层:核心是订阅树(Topic Trie)和路由表,负责回答"这条消息该给谁"。
- 分布式层:集群模式下,决定"该给哪个节点上的谁"。
二、路由的核心:订阅树(Topic Trie)
路由层是整条链路的"大脑"。它维护着一棵订阅树,把每个客户端订阅的过滤器都挂到对应的节点上。
举个例子,假设有四个客户端分别订阅:
客户端 A 订阅:t/+/x 客户端 B 订阅:t/+/y 客户端 C 订阅:t/# 客户端 D 订阅:t/a订阅树长这样:
(root) └── t ├── + ── x → [A] (+ 通配一个层级) ├── + ── y → [B] ├── a → [D] (精确匹配) └── # → [C] (# 通配所有剩余层级)关键理解:这棵树的节点既存"精确的主题段",也存+、#这样的通配段。消息来了,就在树上做一次匹配遍历——这就是为什么第 4 篇说"通配订阅会影响匹配性能":通配节点越多,一次发布要检查的分支就越多。
三、一条消息的完整旅程
把发布和投递串起来,一条t/a/x的 PUBLISH 是这样走的:
拆解成五步:
- 收包解析:连接层收到字节流,按第 6 篇讲的固定头规则解析出"这是一条 PUBLISH,主题
t/a/x,QoS1"。 - 会话处理:会话层先处理这条消息对发布者自己的语义——QoS1 要先回 PUBACK、记录 inflight 状态;QoS2 则要走四拍握手。
- 路由匹配:把主题
t/a/x交给路由层,在订阅树上找出所有匹配的订阅者。本例命中三条:精确的t/a(D)、通配的t/+/x(A)、多层的t/#(C)。 - 投递:对每个命中的订阅者,把消息塞进它的会话队列,再按订阅时的 QoS(还记得第 3 篇说的"min(发布 QoS, 订阅 QoS)"吗)发送。
- 附加动作:如果这条 PUBLISH 带
RETAIN=1,还要更新保留消息索引(第 8 篇的机制);如果命中的订阅在别的节点,则交给分布式层转发。
四、离线与队列:消息去哪排队了
如果某个订阅者(比如 D)此刻不在线,会怎样?这取决于它的会话类型:
- 持久会话 + QoS1/2:消息进离线队列,等客户端重连(同一个 Client ID)后补投递。
- 干净会话 或 QoS0:消息直接丢弃。
- 离线队列会占用 Broker 内存——队列深度没设上限时,海量离线消息就是积压事故的温床(这是第 12 篇故障复盘的根因)。
五、集群模式:跨节点的消息怎么路由
单机版理解透了,集群版只是加了一层"这张订阅表分布在哪"的问题。EMQX 集群用全局路由表记录"哪个主题/过滤器在哪个节点上有订阅者":
- 精确主题用路由表直接查目标节点;
- 通配主题用独立的通配索引(
emqx_route_filters)做匹配; - 路由信息在集群里复制同步,节点宕机时会清理过期路由。
对使用者而言,集群路由是透明的——你往任意节点发布,Broker 自己会把消息送到正确节点上的订阅者。这也是 EMQX 能水平扩展、扛百万连接(第 15 篇会讲)的架构基础。
六、📌 可拿走资产:消息流转架构图
排查视角速查(哪类问题查哪一层):
| 症状 | 重点排查层 | 思路 |
|---|---|---|
| 连接建立失败 | 连接层 | 端口、TLS、认证 |
| 消息丢失 | 会话层 | QoS、会话类型、订阅 QoS |
| 消息没发给预期的人 | 路由层 | 主题/过滤器是否匹配、通配符 |
| 离线消息堆积 | 会话层(队列) | 队列深度、持久会话 |
| 集群下消息不到 | 分布式层 | 路由表、节点状态 |
七、写在最后
恭喜你,到这一篇为止,你已经把 MQTT 从"概念 → 部署 → QoS → 主题 → 报文 → 特性 → 机制 → 扩容 → Broker 内核"完整走了一遍。L2 深度层到此收官。
但从下一篇开始,我们进入这个系列最"值钱"的部分——L3 护城河层:真实生产事故复盘。第一篇我讲的是一次把我半夜叫起来的故障:设备疯狂重连,把 Broker 直接打挂。我会还原完整的时间线、排查弯路,再给你一份指数退避参数模板。这是全网少见的"甲方真实经历"。
🔔 评论区提问:你有没有好奇过"消息明明发了,为什么订阅者收不到"?结合今天讲的四层架构,你遇到过的问题大概卡在哪一层?欢迎留言,一起定位。