2. MQTT协议基础:发布/订阅模型详解、主题(Topic)的概念与层级结构、服务质量(QoS)等级0/1/2、会话(Clean Session)与持久会话
好,咱们正式开始聊MQTT的核心。这一章,我会把MQTT最底层的几个概念掰开揉碎了讲清楚。你想想看,一个协议能成为物联网的事实标准,它的设计哲学一定不简单。
我个人习惯,学任何协议先看它的通信模型。MQTT最迷人的地方,就是它抛弃了传统的客户端-服务器直连模式,引入了一个中间人——Broker(代理服务器)。
2.1 发布/订阅模型:解耦的艺术
传统HTTP通信,客户端直接请求服务器,服务器直接响应。这在物联网里有个大问题:设备A想给设备B发消息,但设备B可能正在睡觉(休眠),或者网络断断续续。怎么办?
MQTT的解决方案是:所有设备都不直接通信,它们只跟Broker打交道。
- 发布者(Publisher):只管把消息扔给Broker,它不关心谁收。
- 订阅者(Subscriber):只管告诉Broker“我对某类消息感兴趣”,然后等着收。
- Broker:负责接收、过滤、转发。它是整个系统的核心。
说白了,这就是一个“消息中介”模式。我在项目中遇到过最典型的场景:一个温度传感器,它只需要每隔5秒发布一次温度值。至于这个数据是被手机App消费了,还是被云端数据库存了,传感器完全不用管。这就是解耦——发布者和订阅者在时间、空间、流量上完全解耦。
核心优势:
- 发布者不需要知道订阅者的IP地址
- 订阅者不需要时刻在线
- 可以轻松实现一对多、多对多的通信
为了让你更直观地理解,我画了一张图。这张图展示了发布/订阅模型的核心流程:
2.2 主题(Topic):消息的“地址”
Broker怎么知道把消息发给谁?靠的就是主题(Topic)。
主题本质上是一个UTF-8字符串,用来对消息进行分类。比如:
sensor/temperature
home/livingroom/light
factory/machine/status
嗯,这里要注意:主题不是路径,它没有物理意义,纯粹是逻辑上的分类。我见过很多新手把主题当成文件路径来设计,结果搞出一堆深层嵌套,维护起来特别痛苦。
2.2.1 层级结构
主题用斜杠 / 分隔层级。比如 factory/machine/status 有三层:工厂、机器、状态。
这种层级结构的好处是,你可以用通配符进行批量订阅。MQTT有两个通配符:
| 通配符 | 名称 | 匹配规则 | 示例 |
|---|---|---|---|
+ |
单层通配符 | 匹配一个层级 | sensor/+/temp 匹配 sensor/room1/temp,但不匹配 sensor/room1/floor/temp |
# |
多层通配符 | 匹配剩余所有层级 | sensor/# 匹配 sensor/temp、sensor/room1/humidity 等所有以 sensor/ 开头的主题 |
避坑指南:我曾经在项目中犯过一个低级错误——用 # 订阅了所有主题,结果Broker把系统内部消息(比如 $SYS/broker/uptime)也推给了客户端。记住,$ 开头的主题是系统保留的,普通通配符不会匹配它,但如果你用 # 订阅 $SYS/#,那就会收到系统消息。设计主题时,建议用 $ 前缀来隔离系统级消息。
2.3 服务质量(QoS):消息能有多靠谱?
物联网网络环境千差万别。有时候WiFi信号好,消息秒到;有时候设备在偏远山区,网络断断续续。MQTT用QoS来解决这个问题。
QoS有三个等级,从0到2,可靠性依次递增,但开销也依次增大。
2.3.1 QoS 0:最多发一次(At most once)
说白了就是“发完拉倒”。发布者把消息扔给Broker,Broker转发给订阅者,双方都不确认。消息可能丢失,但性能最好。
适用场景:传感器高频数据上报,比如温度、湿度。丢一两个数据无所谓,但延迟要低。
2.3.2 QoS 1:至少发一次(At least once)
发布者发送消息后,Broker必须回复一个 PUBACK 确认包。如果发布者没收到 PUBACK,它会重发。订阅者收到消息后,也会回复确认。
这保证了消息至少到达一次,但可能重复。我在项目中遇到过这种情况:网络抖动导致 PUBACK 丢失,发布者重发消息,结果订阅者收到了两条一模一样的指令。如果你的业务逻辑不允许重复,那就要在应用层做去重。
2.3.3 QoS 2:刚好发一次(Exactly once)
这是最可靠的等级,也是开销最大的。它通过两轮握手(4个报文)来保证消息不丢不重。
流程是这样的:
- 发布者发送
PUBLISH(QoS=2) - Broker回复
PUBREC(收到) - 发布者回复
PUBREL(释放) - Broker回复
PUBCOMP(完成)
嗯,这里要注意:QoS 2 的代价是延迟高、带宽占用大。我建议只在关键控制指令上使用,比如“打开阀门”、“关闭电源”。
| QoS等级 | 可靠性 | 是否可能丢失 | 是否可能重复 | 典型场景 |
|---|---|---|---|---|
| 0 | 低 | 是 | 否 | 传感器数据上报 |
| 1 | 中 | 否 | 是 | 状态同步、日志 |
| 2 | 高 | 否 | 否 | 控制指令、支付消息 |
我的建议:不要一上来就用QoS 2。先评估你的业务场景。如果丢一个数据没关系,就用QoS 0。如果必须保证到达但能容忍重复,用QoS 1。只有对数据完整性和唯一性都有严格要求时,才用QoS 2。记住,QoS 2的握手开销是QoS 0的4倍以上。
2.4 会话(Clean Session):连接的状态管理
MQTT客户端连接Broker时,可以决定是否建立一个持久会话。这个选项叫 Clean Session(在MQTT 5.0中改名为 Clean Start)。
2.4.1 Clean Session = true(临时会话)
每次连接都是全新的。Broker不会保存客户端的任何状态。客户端断开后,所有未投递的消息都会丢失。
适用场景:设备每次上线只关心当前数据,不关心离线期间发生了什么。比如一个只显示实时数据的仪表盘。
2.4.2 Clean Session = false(持久会话)
Broker会保存客户端的订阅信息和离线消息。客户端下次上线时,Broker会把离线期间积压的消息推送给它。
为什么会这样?因为物联网设备经常掉线。如果设备掉线期间,其他设备发布了它关心的消息,等它重新上线时,Broker还能把这些消息补发给它。
持久会话的存储内容:
- 客户端的订阅列表
- QoS 1 和 QoS 2 的未确认消息
- 客户端离线期间,以 QoS 1 或 QoS 2 发布的、匹配该客户端订阅的消息
我曾经在项目中遇到一个坑:一个设备设置了持久会话,但离线了整整一周。结果Broker里积压了几十万条消息,设备一上线,Broker疯狂推送,直接把设备的网络带宽打满了。所以,使用持久会话时,一定要设置消息过期时间(Message Expiry Interval),或者限制积压消息的数量。
避坑指南:如果你用持久会话,记得在客户端断开时主动发送 DISCONNECT 报文。否则Broker会认为客户端意外掉线,继续保留会话。另外,持久会话的 Client ID 必须唯一。如果两个客户端用同一个 Client ID 连接,前一个会被踢下线。
好了,这一章的内容就到这里。发布/订阅模型、主题、QoS、会话,这四个概念是MQTT的基石。理解透了,后面的高级特性学起来就轻松了。