2. MQTT核心原理(上):发布/订阅模型、主题(Topic)与通配符、服务质量(QoS 0/1/2)详解
好,咱们正式开始啃MQTT的核心原理。这一节内容,说白了就是MQTT的“内功心法”。你后面写代码能不能稳,能不能处理各种奇葩情况,全看这节理解得透不透。
我个人习惯,学一个新协议,先别急着看代码,先把它的“世界观”搞明白。MQTT的世界观,就是三个词:发布、订阅、主题。
2.1 发布/订阅模型:告别“点对点”的思维定势
传统的网络通信,比如HTTP,是典型的“请求-响应”模式。客户端问,服务器答。这就像你打电话,一对一,实时性要求高,但耦合也强。
MQTT不一样。它用的是发布/订阅(Pub/Sub)模型。想象一下微信群聊:
- 发布者(Publisher):就是发消息的人。他不需要知道谁在看,只管往群里(主题)丢消息。
- 订阅者(Subscriber):就是看消息的人。他只关心自己感兴趣的群(主题),消息来了就能收到。
- 代理(Broker):就是微信群服务器。它负责接收发布者的消息,然后转发给所有订阅了这个群的订阅者。
你看,发布者和订阅者之间,完全解耦。他们不需要知道对方的存在,也不需要同时在线。这就是MQTT最核心的优势。
核心要点: 发布/订阅模型打破了传统的“客户端-服务器”直连模式,实现了空间、时间、同步的三维解耦。这在物联网场景下,简直是神器。
我在项目中遇到过一种情况:一个传感器节点采集数据,需要同时发给云端数据库、本地显示屏幕和报警系统。如果用HTTP,你得写三个不同的客户端,分别去调用三个接口。用MQTT?传感器只管往“sensor/data”这个主题发一条消息,三个订阅者各自收自己的,代码量直接砍半。
2.2 主题(Topic):MQTT世界的“地址”
主题,就是MQTT消息的“标签”或“地址”。它决定了消息该去哪里,订阅者该听什么。
主题是一个字符串,用“/”分隔层级。比如:
home/livingroom/temperature
factory/line1/machine3/status
sensor/+/temperature
这里有几个设计原则,我建议你记牢:
- 层级清晰:用“/”划分层级,从宽泛到具体。比如“工厂/车间/设备/参数”。
- 不要以“/”开头或结尾:虽然协议允许,但容易引起歧义。我个人习惯统一不加。
- 避免空格和特殊字符:主题里最好只用字母、数字、下划线、横线。否则调试的时候,有你哭的。
- 区分大小写:“Home/Temp”和“home/temp”是两个完全不同的主题。这一点,我踩过坑。
我的小技巧: 设计主题时,可以按照“范围/位置/设备/属性”的格式来。比如“factory/shenzhen/robot_arm_01/motor_temp”。这样后期扩展和维护,都特别清晰。
2.3 通配符:一招搞定批量订阅
假设你有一个智能家居系统,家里有10个温度传感器。难道你要一个一个去订阅“home/bedroom/temp”、“home/livingroom/temp”……?那也太傻了。
MQTT提供了两个通配符,专门解决这个问题:
| 通配符 | 名称 | 含义 | 示例 |
|---|---|---|---|
+ |
单级通配符 | 匹配一个层级 | home/+/temp 匹配 home/bedroom/temp,但不匹配 home/bedroom/floor/temp |
# |
多级通配符 | 匹配剩余所有层级 | home/# 匹配 home/bedroom/temp、home/livingroom/light/status 等所有以 home/ 开头的主题 |
举个例子:
- 订阅
sensor/+/temperature:你会收到sensor/1/temperature和sensor/2/temperature,但收不到sensor/1/humidity。 - 订阅
sensor/#:你会收到所有以sensor/开头的消息,不管后面有多少层级。
注意! # 通配符必须放在主题的最后,而且只能出现一次。比如 home/#/temp 是非法的。我曾经在调试一个农业大棚项目时,就因为多级通配符放错了位置,导致订阅了一堆垃圾消息,排查了半天。
2.4 服务质量(QoS):消息到底靠不靠谱?
物联网环境,网络不稳定是常态。WiFi断连、信号弱、丢包……MQTT怎么保证消息能送到?靠的就是服务质量(QoS)。
MQTT定义了三个等级:
| QoS等级 | 名称 | 可靠性 | 网络开销 | 适用场景 |
|---|---|---|---|---|
| 0 | 至多一次 | 最低 | 最小 | 传感器数据(丢一帧无所谓)、日志 |
| 1 | 至少一次 | 中等 | 中等 | 控制指令(必须执行,但允许重复) |
| 2 | 恰好一次 | 最高 | 最大 | 计费、支付、关键告警 |
咱们一个一个说:
QoS 0:至多一次(Fire and Forget)
说白了,就是“发出去就不管了”。Broker收到消息后,不会回复任何确认。发送方也不知道消息到底到没到。这是效率最高的方式,但也是最不可靠的。
我建议:大部分传感器数据,用QoS 0就够了。比如温度、湿度,每秒上报一次,丢个一两帧,完全不影响大局。
QoS 1:至少一次(At Least Once)
这个等级,Broker收到消息后,会回复一个PUBACK包。如果发送方没收到PUBACK,就会重发。所以,消息至少会被送达一次,但可能重复。
嗯,这里要注意:重复消息,需要接收方自己做去重处理。我在一个智能灯控项目里,就因为这个吃了亏。灯控指令用了QoS 1,结果网络抖动,指令重复发送,灯在那里闪个不停……后来我在应用层加了个消息ID去重,才解决。
QoS 2:恰好一次(Exactly Once)
这是最严格的等级。它通过两次确认(PUBREC、PUBREL、PUBCOMP)来保证消息既不丢失,也不重复。代价就是网络交互次数最多,延迟最大。
为什么会这样?因为MQTT需要确保发送方和Broker之间,以及Broker和接收方之间,都达成“恰好一次”的共识。这背后的协议细节,咱们后面讲代码的时候再细说。
我的建议: 在单片机上,优先使用QoS 0。如果确实需要可靠性,用QoS 1,并在应用层做去重。QoS 2,除非是极其关键的场景(比如门锁控制、支付扣款),否则尽量别用。因为它的内存开销和网络开销,对资源受限的单片机来说,是个不小的负担。
好了,这一节的内容就到这。发布/订阅模型、主题与通配符、QoS,这三个概念是MQTT的基石。你理解了它们,后面看代码、调Bug,心里就有底了。下一节,咱们接着聊MQTT的连接、心跳和遗嘱,这些都是实战中绕不开的坑。