☰
一条MQTT消息在EMQX内部怎么流转:订阅树、主题匹配、会话队列(架构视角)
2026/10/23 2:55:55 网站建设 项目流程

一条MQTT消息在EMQX内部怎么流转:订阅树、主题匹配、会话队列(架构视角)

前面几篇我们都在"Broker 外面"看 MQTT。这一篇钻进去:一条 PUBLISH 消息从设备发出来,在 EMQX 内部到底经历了哪些关卡,才最终到达每一个订阅者?看懂这条链路,你就不再是"会配会用的使用者",而是"能定位问题在哪个环节"的架构者。本文以 EMQX 5.x 的分层架构为例,思路对其它 Broker(Mosquitto、HiveMQ)同样适用。


一、先看 EMQX 的分层架构

EMQX 在设计上把职责分成清晰的几层(每层专注一件事):

接入

设备1

连接层 Connection Layer
管理 TCP 连接、收发字节流

设备2

会话层 Session Layer
每个客户端一个会话:订阅关系、
inflight 消息、离线队列

路由层 Route Layer
订阅树 Topic Trie + 路由表
做主题匹配

分布式层 Distributed Layer
集群路由、跨节点转发

会话/队列存储

路由表
emqx_route / emqx_route_filters

记住这四层的分工,整条链路就清晰了:

  • 连接层:只负责"把字节收进来、把字节发出去",不关心消息内容。
  • 会话层:为每个客户端维护一个会话——它订阅了什么、哪些 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 是这样走的:

订阅者D(t/a)订阅者C(t/订阅者A(t/+/x)路由层会话层连接层设备(发布者)订阅者D(t/a)订阅者C(t/订阅者A(t/+/x)路由层会话层连接层设备(发布者)命中:t/a → D;t/+/x → A;t/PUBLISH topic=t/a/x, QoS1解析报文,交给会话层处理 QoS1 确认、更新 inflight问:"t/a/x 该发给谁?"遍历订阅树匹配返回匹配的订阅者列表 [A, C, D]投递(入 A 的会话队列 → 按 QoS 发出)投递投递

拆解成五步:

  1. 收包解析:连接层收到字节流,按第 6 篇讲的固定头规则解析出"这是一条 PUBLISH,主题t/a/x,QoS1"。
  2. 会话处理:会话层先处理这条消息对发布者自己的语义——QoS1 要先回 PUBACK、记录 inflight 状态;QoS2 则要走四拍握手。
  3. 路由匹配:把主题t/a/x交给路由层,在订阅树上找出所有匹配的订阅者。本例命中三条:精确的t/a(D)、通配的t/+/x(A)、多层的t/#(C)。
  4. 投递:对每个命中的订阅者,把消息塞进它的会话队列,再按订阅时的 QoS(还记得第 3 篇说的"min(发布 QoS, 订阅 QoS)"吗)发送。
  5. 附加动作:如果这条 PUBLISH 带RETAIN=1,还要更新保留消息索引(第 8 篇的机制);如果命中的订阅在别的节点,则交给分布式层转发。

四、离线与队列:消息去哪排队了

如果某个订阅者(比如 D)此刻不在线,会怎样?这取决于它的会话类型:

在线

离线

干净会话

持久会话

路由层匹配到 D

D 在线吗?

直接投递

会话类型

丢弃(QoS0 也一样丢)

入 D 的离线队列
等 D 重连后补投递

  • 持久会话 + QoS1/2:消息进离线队列,等客户端重连(同一个 Client ID)后补投递。
  • 干净会话 或 QoS0:消息直接丢弃。
  • 离线队列会占用 Broker 内存——队列深度没设上限时,海量离线消息就是积压事故的温床(这是第 12 篇故障复盘的根因)。

五、集群模式:跨节点的消息怎么路由

单机版理解透了,集群版只是加了一层"这张订阅表分布在哪"的问题。EMQX 集群用全局路由表记录"哪个主题/过滤器在哪个节点上有订阅者":

全局路由表

Node1

本地订阅者直接投递

跨节点转发

跨节点转发

发布者 → Node1

t/a → Node1, Node3
t/# → Node2

Node1 订阅者

Node2 订阅者

Node3 订阅者

  • 精确主题用路由表直接查目标节点;
  • 通配主题用独立的通配索引(emqx_route_filters)做匹配;
  • 路由信息在集群里复制同步,节点宕机时会清理过期路由。

对使用者而言,集群路由是透明的——你往任意节点发布,Broker 自己会把消息送到正确节点上的订阅者。这也是 EMQX 能水平扩展、扛百万连接(第 15 篇会讲)的架构基础。


六、📌 可拿走资产:消息流转架构图

Broker 内部

PUBLISH

RETAIN=1

跨节点

发布者

EMQX Broker

连接层
收包解析

会话层
QoS确认/状态

路由层
订阅树匹配

会话队列
按订阅QoS投递

保留消息索引

分布式层
集群路由

订阅者1

订阅者2

订阅者N

排查视角速查(哪类问题查哪一层):

症状重点排查层思路
连接建立失败连接层端口、TLS、认证
消息丢失会话层QoS、会话类型、订阅 QoS
消息没发给预期的人路由层主题/过滤器是否匹配、通配符
离线消息堆积会话层(队列)队列深度、持久会话
集群下消息不到分布式层路由表、节点状态

七、写在最后

恭喜你,到这一篇为止,你已经把 MQTT 从"概念 → 部署 → QoS → 主题 → 报文 → 特性 → 机制 → 扩容 → Broker 内核"完整走了一遍。L2 深度层到此收官。

但从下一篇开始,我们进入这个系列最"值钱"的部分——L3 护城河层:真实生产事故复盘。第一篇我讲的是一次把我半夜叫起来的故障:设备疯狂重连,把 Broker 直接打挂。我会还原完整的时间线、排查弯路,再给你一份指数退避参数模板。这是全网少见的"甲方真实经历"。


🔔 评论区提问:你有没有好奇过"消息明明发了,为什么订阅者收不到"?结合今天讲的四层架构,你遇到过的问题大概卡在哪一层?欢迎留言,一起定位。

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

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

立即咨询