MQTT 协议详解:Topic、QoS、遗嘱消息与保留消息
- 电子技术
- 7小时前
- 23热度
- 0评论
MQTT 协议是物联网场景中应用极广的轻量级消息协议,基于发布/订阅模型,专为低带宽、不稳定网络和资源受限设备设计。本文按「协议定位 → 连接存活 → 消息可靠性 → 异常断线 → 状态同步」的顺序,系统梳理 MQTT 协议的五个关键工程机制:Topic 设计、Keep Alive、QoS、遗嘱消息和保留消息。
- MQTT 是应用层协议,工作在 TCP/IP 之上
- 它以 Broker 为中心,采用发布/订阅模型
- 它和 HTTP 同属应用层,但 HTTP 是请求/响应,MQTT 是发布/订阅
- 理解 MQTT 是否「靠谱」,重点要看 Topic 设计、Keep Alive、QoS、遗嘱消息、保留消息
- 这五个机制相互配合,共同决定系统在弱网、掉线、重连场景下是否可用
一、MQTT 是什么
MQTT(Message Queuing Telemetry Transport)最早由 IBM 相关团队于 1999 年提出,目标是为低带宽、不稳定网络、资源受限设备提供轻量消息传输,后来成为物联网设备通信的事实标准之一。
1.1 与 HTTP 的区别
| 对比项 | HTTP | MQTT |
|---|---|---|
| 通信模型 | 请求/响应 | 发布/订阅 |
| 消息分发 | 一对一点对点 | 天然支持一对多、多对多 |
| 中心角色 | Server | Broker |
| 典型连接 | 短连接或长连接 | 常见为长连接 |
| 典型场景 | 页面访问、接口调用 | 实时消息分发、设备状态同步 |
两者最本质的差别在于 消息组织方式与系统耦合方式不同:HTTP 更接近一对一调用,MQTT 更适合一对多甚至多对多的消息分发。
1.2 四个核心角色
- Publisher(发布者):负责发送消息
- Broker(代理服务器):负责接收、路由和转发消息
- Subscriber(订阅者):负责接收自己关心的消息
- Topic(主题):用于给消息分流和分类
1.3 工作流程
- 发布者把消息发到某个 Topic
- Broker 收到消息后,根据 Topic 找到对应订阅者
- Broker 把消息转发给订阅了该 Topic 的客户端
MQTT 的核心是:发布者把消息交给 Broker,由 Broker 按 Topic 路由给订阅者,客户端之间不直接通信。
1.4 基础术语
| 术语 | 含义 | 说明 |
|---|---|---|
| Client | MQTT 客户端 | 可以是设备、APP、服务端程序、网页等 |
| Broker | 消息代理服务器 | 负责接收、保存、路由和转发消息 |
| Topic | 消息主题 | 消息的分类路径,例如 home/room1/status;支持 +(单层)和 #(多层)通配符 |
| Payload | 消息载荷 | 消息正文,可以是文本、JSON、二进制 |
| Session | 会话状态 | Broker 可为客户端保存订阅、未确认消息等会话信息 |
| Keep Alive | 保活机制 | MQTT 协议层用于检测长连接是否仍然有效的机制 |
二、Topic:消息的地址与路由
2.1 Topic 是谁创建的?怎么来的?
MQTT 里的 Topic 不需要提前创建,这是初学者最容易产生困惑的地方之一。
和数据库不同,不需要先建表,也不需要在 Broker 里预先注册。Topic 是在第一次使用时自动生效的:
- 客户端向某个 Topic 发布(publish) 消息,这个 Topic 就在 Broker 的消息流中存在了
- 客户端订阅(subscribe) 某个 Topic,Broker 就记住这个订阅关系
- Broker 不需要提前知道有哪些 Topic,也不维护一张 Topic 注册列表
2.2 谁来定义 Topic?
Topic 由开发者自己约定,MQTT 协议本身不强制任何命名规范。常见约定是用 / 分隔层级,形成树状结构:
home/room1/temperature
device/001/status
factory/line2/machine3/alarm
这套命名完全由团队自行决定。一旦约定,发布端和订阅端必须严格一致,否则消息根本不会匹配。
device/001/Status 和 device/001/status 是两个不同的 Topic。
2.3 Broker 没有这个 Topic 怎么办?
不会报错,也不需要任何预先配置。Broker 的处理逻辑很简单:
- 有人 publish → Broker 转发给当前订阅了该 Topic 的客户端
- 没有人订阅 → 消息直接丢弃(除非有 retain 或离线会话机制兜底)
- 没有人 publish → 这个 Topic 就不活跃,不占用任何资源
因此,「Broker 里有没有某个 Topic」这个问题本身意义不大——Topic 是动态的,用到才存在。
2.4 通配符:怎么订阅多个 Topic
MQTT 支持两种通配符,用于一次订阅匹配多个 Topic。
+:单层通配符
只匹配当前一层,不跨层级。
home/+/temperature
能匹配:
home/room1/temperaturehome/room2/temperature
不能匹配:
home/room1/sensor/temperature(多了一层)home/temperature(少了一层)
+ 可以出现在路径的任意层,也可以多次出现:
home/+/+/status
#:多层通配符
匹配当前及后续所有层级,只能放在末尾。
home/#
能匹配:
home/room1home/room1/temperaturehome/room1/sensor/humidity
订阅 # 即订阅 Broker 上所有 Topic,请谨慎使用,流量可能非常大。
通配符对比
| 通配符 | 匹配范围 | 可以放在中间吗 | 典型用途 |
|---|---|---|---|
+ |
单层任意 | 可以 | 匹配同结构不同设备/房间 |
# |
当前层及以下所有层 | 不可以,只能在末尾 | 订阅某设备/某区域所有消息 |
三、Keep Alive:连接存活检测
3.1 Keep Alive 到底是什么
Keep Alive 可以理解为 MQTT 对长连接的一种「存活检查」约定。
它不是底层传输本身,更不是「替代 TCP」的机制;更准确地说,它是 建立在 TCP 连接之上的 MQTT 协议层保活机制。
可以这样理解:
- 客户端和 Broker 会约定一个 Keep Alive 时间,例如
60 秒 - 在这段时间内,客户端至少要让 Broker 看到一次 MQTT 报文
- 如果这段时间里没有正常业务报文,客户端通常会发送一个
PINGREQ - Broker 收到后会返回
PINGRESP - 如果 Broker 长时间没有等到这些报文,就会认为连接已经失效
PINGREQ / PINGRESP 更像是在业务空闲时证明连接还活着。
3.2 Keep Alive、心跳与遗嘱消息的区别
| 概念 | 它是什么 | 主要作用 | 典型表现 |
|---|---|---|---|
| Keep Alive | MQTT 协议层的保活约定 | 帮 Broker 判断连接是否仍然有效 | 约定超时时间,必要时触发 PINGREQ / PINGRESP |
| 心跳 | 更口语化的说法 | 泛指「定期证明自己还活着」的动作 | 在 MQTT 里通常就是空闲时的 PINGREQ / PINGRESP |
| 遗嘱消息(LWT) | 客户端预先交给 Broker 的离线通知 | 在异常断线后通知其他订阅方 | Broker 检测到失联后代发 offline 等状态消息 |
Keep Alive 负责「查岗」
心跳 是查岗时的动作
遗嘱消息 是查岗发现人不在后,对外发出的通知
四、QoS:消息可靠性
4.1 QoS 是什么
QoS(Quality of Service,服务质量等级)是 MQTT 协议的一项关键功能,定义了发布者与订阅者之间消息传递的保证级别。
MQTT 提供三个等级:
- QoS 0:At most once,最多一次
- QoS 1:At least once,至少一次
- QoS 2:Exactly once,只有一次
这里的重点是:QoS 定义的是协议层的投递语义,不是「业务结果一定正确」。
QoS 使客户端能够选择与其网络可靠性和应用逻辑相匹配的服务级别。由于 MQTT 在协议层管理消息的重传并保证送达(即使底层传输不可靠),QoS 使得在弱网环境中的通信变得更容易。
4.2 三个等级分别意味着什么
| 等级 | 名称 | 含义 | 是否可能丢消息 | 是否可能重复 | 网络开销 | 典型场景 |
|---|---|---|---|---|---|---|
| QoS 0 | 最多一次 (At most once) | 尽力而为,不确认是否到达 | 可能丢失 | 不重复 | 最低 | 传感器高频上报、非关键数据 |
| QoS 1 | 至少一次 (At least once) | 确保消息至少到达一次 | 不丢失 | 可能重复 | 中等 | 命令、告警、状态变更 |
| QoS 2 | 仅一次 (Exactly once) | 确保消息到达且仅到达一次 | 不丢失 | 不重复 | 最高 | 金融交易、计费系统 |
4.3 工程上怎么理解 QoS
QoS 0
适合对单条消息不敏感、允许偶发丢失的数据流,例如:
- 温湿度周期上报
- 高频遥测数据
- 刷新频率较高的状态采样
特点是:轻量、吞吐高,但不保证一定送达。
QoS 1
适合「希望尽量送达,但允许接收端去重」的场景,例如:
- 远程控制命令
- 业务通知
- 设备状态变更事件
特点是:可靠性更高,但接收方必须考虑重复消息问题。
QoS 2
适合对重复和丢失都高度敏感的场景,例如:
- 计费
- 结算
- 某些强一致性要求的业务消息
特点是:最可靠,但开销最大,实现最复杂。
4.4 使用 QoS 时的几个边界
- QoS 是发布者和 Broker 之间、Broker 和订阅者之间分别协商的,最终实际语义取两段中的较低值
- QoS 1 / 2 不等于「离线消息补投」,是否能补投还取决于会话状态(cleanSession)和 Broker 配置
- 消息是否可恢复,还与会话持久化和 Broker 配置有关
- 遥测上报常用 QoS 0 / 1
- 控制命令常用 QoS 1
- QoS 2 只在确有必要时使用
五、遗嘱消息:异常断线感知
5.1 遗嘱消息是什么
遗嘱消息(Last Will and Testament, LWT)可以理解为:客户端在连接建立时,提前托付给 Broker 的一条「如果我异常下线,请帮我发出去」的消息。
5.2 遗嘱消息解决什么问题
在正常断开连接时,客户端可以主动发送 DISCONNECT 报文,Broker 知道客户端是主动离线的。
但如果客户端是异常断线(网络中断、设备崩溃、掉电等),Broker 不会收到 DISCONNECT,此时就需要遗嘱消息来通知其他订阅方。
5.3 遗嘱消息的工作流程
- 客户端在 CONNECT 报文中预先设置好遗嘱消息(Topic、Payload、QoS、Retain)
- 正常使用期间,遗嘱消息存放在 Broker 中
- Broker 检测到客户端异常断线(Keep Alive 超时)
- Broker 将遗嘱消息发布到对应 Topic
- 订阅了该 Topic 的客户端收到离线通知
5.4 遗嘱消息的典型用途
最常见的用途是设备在线状态监控:
- 连接时设置遗嘱消息:Topic
device/001/status,Payloadoffline,retain=true - 连接成功后主动发布
online(retain=true) - 一旦异常掉线,Broker 自动发布
offline
这样其他订阅方就能实时感知设备是否在线。
5.5 遗嘱消息的几个边界
- 遗嘱消息是异常断线才触发,正常 DISCONNECT 不会触发
- Broker 是在检测到 Keep Alive 超时后才发送遗嘱消息,有一定延迟
- 遗嘱消息不是瞬间触发,延迟取决于 Keep Alive 超时时间
- 可以结合 retain=true 让新订阅者也能立即看到最新状态
六、保留消息:新订阅者状态引导
6.1 保留消息是什么
普通 MQTT 消息默认只发送给「当前已订阅且在线」的客户端。
保留消息(Retained Message)则表示:
- Broker 会为某个 Topic 保存 最后一条带 retain 标志的消息
- 以后新的订阅者一旦订阅这个 Topic,会立刻收到这条最新消息
因此,Retain 的本质是:为某个 Topic 提供最近一次有效状态的缓存。
6.2 它解决的核心问题
保留消息解决的是:新订阅者能否立刻获得当前状态,而不必等待下一次上报。
例如:
- 控制面板刚打开时,想立即知道设备当前是开还是关
- 新接入的服务想马上知道某个传感器的最近一次读数
- 运维面板想立刻拿到某设备最后一次上报的状态
6.3 Retain 适合什么内容
适合 Retain 的通常是「当前状态型」数据,例如:
device/001/status = online/offlinedevice/001/led/state = on/offdevice/001/sensor/temperature = 26.5
这些数据的共同特点是:有「当前值」语义,新订阅者拿到最新值有意义,不需要完整历史,只需要最近状态。
6.4 Retain 不适合什么内容
一般不适合 Retain 的内容包括:
- 历史日志
- 事件流
- 一次性通知
- 一次性执行命令(例如
reboot、unlock、start)
通常建议:状态 Topic 可以 retained,命令 Topic 一般不要 retained。
6.5 Retain 的一个重要边界
- Retained Message 不是历史数据库
- 一个 Topic 通常只保留「最后一条 retained 消息」
- 它解决的是「当前状态同步」,不是「历史消息追溯」
如果要清除某个 Topic 的 retained 消息,常见做法是:
- 向该 Topic 发布一条 空 Payload 且 retain=true 的消息,让 Broker 删除该 retained 状态
七、综合设计:五个机制如何配合
前面这些机制最好不要孤立理解。在实际项目里,它们分别承担不同职责,通常配合使用:
- Topic:消息的地址,决定消息发往哪里、谁能收到
- Keep Alive:帮助 Broker 判断连接是否失效
- QoS:决定消息按什么可靠性语义传递
- 遗嘱消息:在异常断线后对外发布状态通知
- 保留消息:让新订阅者一进来就拿到最近状态
7.1 常见组合设计
| Topic 类型 | 示例 | QoS 建议 | Retain 建议 | 说明 |
|---|---|---|---|---|
| 遥测数据 | device/001/sensor/temp |
QoS 0 / 1 | 可选 | 关注吞吐与实时性 |
| 在线状态 | device/001/status |
QoS 1 | 建议开启 | 常与遗嘱消息联用 |
| 设备当前状态 | device/001/led/state |
QoS 1 | 建议开启 | 便于新客户端立即同步 |
| 控制命令 | device/001/led/set |
QoS 1 | 一般关闭 | 避免旧命令重放 |
| 强一致业务消息 | billing/tx/001 |
QoS 2 | 按需 | 只在确有必要时使用 |
7.2 典型的设备在线状态设计
以设备在线状态为例,一个较完整的设计通常是:
- 状态 Topic:
device/001/status - 遗嘱消息:
offline,QoS 1,retain=true - 连接成功后主动发布:
online,retain=true - Keep Alive 设置为合理值,例如
60s
这样可以同时满足:
- 连接失效可被检测:Broker 可借助 Keep Alive 发现客户端是否失联
- 异常掉线可见:Broker 能自动发
offline - 新订阅者立即感知状态:一订阅就拿到最近状态
- 状态语义清晰:
statusTopic 只承担在线状态职责
7.3 速查表
| 关注点 | 对应机制 | 核心作用 | 典型问题 |
|---|---|---|---|
| Topic 如何组织 | Topic 命名 + 通配符 | 分类消息、控制订阅粒度 | 命名随意导致难以维护,或通配符滥用 |
| 连接是否仍存活 | Keep Alive | 帮 Broker 判断长连接是否失效 | 把它等同于业务心跳,或超时时间设置不合理 |
| 消息可靠性 | QoS | 定义投递语义 | 选太低会丢,选太高会贵 |
| 异常断线感知 | 遗嘱消息 | Broker 代发离线通知 | 不配置就容易误判在线 |
| 新订阅者状态同步 | 保留消息 | 提供最近一次有效状态 | 配错 Topic 会造成旧状态 / 旧命令问题 |
八、常见误区与总结
- 随意命名 Topic,缺乏层级规划,后期难以用通配符批量订阅
- 误以为 Topic 需要提前在 Broker 注册或创建
- 给命令 Topic 使用通配符订阅,导致误收其他设备命令
- 一律使用 QoS 2,结果把系统复杂度和性能开销拉高
- 只在上线时发布
online,却没有配置遗嘱消息,导致离线状态不可信 - 把 Retain 当「历史消息存储」使用
- 给命令 Topic 开启 retained,导致新客户端收到旧命令
- 以为 QoS 1 就不需要幂等处理
- 把 Keep Alive 和业务层心跳完全混为一谈
- Keep Alive 设置过短或过长都不合适:过短可能在弱网环境误判掉线,过长又会让离线发现滞后
- 以为设备断网后,遗嘱消息一定会瞬间触发
- 以为 QoS 1 / 2 天然等于「离线消息补投」,忽略了会话和 Broker 配置
如果把 MQTT 看成一个长期在线、长期通信的消息系统,那么这几个概念分别承担不同职责:
- Topic:负责消息分类与路由,通配符控制订阅粒度
- Keep Alive:负责连接存活判断
- QoS:负责消息投递语义
- 遗嘱消息:负责异常断线通知
- 保留消息:负责最新状态同步
只会发布 / 订阅,而不理解这些机制的边界与组合方式,系统通常只是「能通」;只有把它们设计清楚,系统才更接近「可用、可维护、可扩展」。
- Topic:消息发往哪里?谁能收到?用通配符怎么订阅?
- Keep Alive:这条 MQTT 长连接还活着吗?
- QoS:这条消息传得靠不靠谱?
- 遗嘱消息:客户端异常掉线,别人能不能知道?
- 保留消息:新订阅者一进来,能不能立刻拿到当前状态?
九、巩固练习:常见考题
概念理解
Q1:MQTT 和 HTTP 最本质的区别是什么?
查看答案
HTTP 是请求/响应模型,通信双方直接耦合;MQTT 是发布/订阅模型,发布者和订阅者通过 Broker 解耦,天然支持一对多消息分发。
Q2:MQTT 的 Topic 需要提前在 Broker 上创建吗?
查看答案
不需要。Topic 是动态的,第一次有人 publish 或 subscribe 时自动生效,Broker 不维护 Topic 注册列表。
Q3:通配符 + 和 # 有什么区别?home/+/temperature 能匹配 home/room1/sensor/temperature 吗?
查看答案
- + 只匹配**单层**,# 匹配**当前及以下所有层**,且只能放在末尾
- home/+/temperature **不能**匹配 home/room1/sensor/temperature,因为中间多了一层 sensor
- 要匹配该路径,应用 home/+/+/temperature 或 home/#
Q4:Keep Alive 和业务层心跳是同一个东西吗?
查看答案
不是。Keep Alive 是 MQTT 协议层的保活约定,只要有正常 MQTT 报文来往就不需要额外发 PINGREQ;业务层心跳是应用自定义的定期上报逻辑。两者目的和层次都不同,不能混为一谈。
QoS 选择
Q5:以下场景各应选哪个 QoS?
| 场景 | 你的选择 |
|---|---|
| 温湿度传感器每秒上报一次,允许偶发丢失 | ? |
| 远程控制设备开关,不允许丢失,允许重复(接收端做幂等) | ? |
| 金融系统计费消息,不能丢、不能重复 | ? |
查看答案
| 场景 | 答案 |
| :--- | :--- |
| 温湿度传感器每秒上报 | QoS 0 |
| 远程控制开关 | QoS 1 |
| 金融计费消息 | QoS 2 |
Q6:使用 QoS 1 是否意味着设备离线期间的消息一定能补投?
查看答案
不一定。QoS 1 只定义协议层的投递语义,是否能在离线期间缓存并补投,还取决于:
- 客户端是否使用持久会话(cleanSession=false)
- Broker 是否支持并开启了离线消息队列
遗嘱消息与保留消息
Q7:遗嘱消息一定会在设备断网后立刻触发吗?
查看答案
不会。Broker 需要等 Keep Alive 超时后才判断连接失效,此时才发送遗嘱消息。延迟通常等于 Keep Alive 时间的 1.5 倍左右,可能长达数十秒甚至更久。
Q8:如果把 device/001/reboot(重启命令 Topic)设置为 retain=true,会有什么问题?
查看答案
新上线的设备一订阅该 Topic,就会立即收到上次发布的旧重启命令,导致设备误重启。命令类 Topic 一般不应开启 retain,只有「当前状态型」Topic 才适合使用 retain。
Q9:如何清除某个 Topic 上的 retained 消息?
查看答案
向该 Topic 发布一条 **Payload 为空且 retain=true** 的消息,Broker 会删除该 Topic 的 retained 状态。
综合设计
Q10:设计一个设备在线状态系统,要求:新订阅者立即知道设备是否在线,设备异常掉线后其他客户端能收到通知。需要用到哪些机制?如何配置?
查看答案
需要组合使用 Keep Alive + 遗嘱消息 + 保留消息:
- **状态 Topic**:device/001/status
- **遗嘱消息**:Payload offline,QoS 1,retain=true(异常断线时 Broker 自动发布)
- **连接成功后**:主动发布 online,QoS 1,retain=true
- **Keep Alive**:设置合理值,例如 60s
这样可以同时满足:异常掉线可被感知、新订阅者立即拿到当前状态、状态持久可查。
延伸阅读
相关笔记
- [[98-博客/mqtt.md|MQTT 入门笔记]]
- [[98-博客/通信基础概念wp.md|通信基础概念]]
- [[98-博客/总线特点和区别.md|总线特点和区别]]