MQTT 协议详解:Topic、QoS、遗嘱消息与保留消息

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 工作流程

  1. 发布者把消息发到某个 Topic
  2. Broker 收到消息后,根据 Topic 找到对应订阅者
  3. Broker 把消息转发给订阅了该 Topic 的客户端

MQTT 的核心是:发布者把消息交给 Broker,由 Broker 按 Topic 路由给订阅者,客户端之间不直接通信。

1.4 基础术语

术语 含义 说明
Client MQTT 客户端 可以是设备、APP、服务端程序、网页等
Broker 消息代理服务器 负责接收、保存、路由和转发消息
Topic 消息主题 消息的分类路径,例如 home/room1/status;支持 +(单层)和 #(多层)通配符
Payload 消息载荷 消息正文,可以是文本、JSON、二进制
Session 会话状态 Broker 可为客户端保存订阅、未确认消息等会话信息
Keep Alive 保活机制 MQTT 协议层用于检测长连接是否仍然有效的机制
ℹ️ 一个重要认知:MQTT 里很多「是否能收到消息」的问题,并不只取决于消息有没有发出去,还与 QoS、会话状态、订阅关系、Retain 标志、Broker 配置、Keep Alive 等因素有关。

二、Topic:消息的地址与路由

2.1 Topic 是谁创建的?怎么来的?

MQTT 里的 Topic 不需要提前创建,这是初学者最容易产生困惑的地方之一。

和数据库不同,不需要先建表,也不需要在 Broker 里预先注册。Topic 是在第一次使用时自动生效的:

  • 客户端向某个 Topic 发布(publish) 消息,这个 Topic 就在 Broker 的消息流中存在了
  • 客户端订阅(subscribe) 某个 Topic,Broker 就记住这个订阅关系
  • Broker 不需要提前知道有哪些 Topic,也不维护一张 Topic 注册列表
💡 类比理解:Topic 更像一个「频道标签」——有人往这个频道发消息,有人收听这个频道,Broker 负责中间转发,仅此而已。

2.2 谁来定义 Topic?

Topic 由开发者自己约定,MQTT 协议本身不强制任何命名规范。常见约定是用 / 分隔层级,形成树状结构:

home/room1/temperature
device/001/status
factory/line2/machine3/alarm

这套命名完全由团队自行决定。一旦约定,发布端和订阅端必须严格一致,否则消息根本不会匹配。

⚠️ 注意:Topic 区分大小写。device/001/Statusdevice/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/temperature
  • home/room2/temperature

不能匹配:

  • home/room1/sensor/temperature(多了一层)
  • home/temperature(少了一层)

+ 可以出现在路径的任意层,也可以多次出现:

home/+/+/status

#:多层通配符

匹配当前及后续所有层级,只能放在末尾。

home/#

能匹配:

  • home/room1
  • home/room1/temperature
  • home/room1/sensor/humidity

订阅 # 即订阅 Broker 上所有 Topic,请谨慎使用,流量可能非常大。

通配符对比

通配符 匹配范围 可以放在中间吗 典型用途
+ 单层任意 可以 匹配同结构不同设备/房间
# 当前层及以下所有层 不可以,只能在末尾 订阅某设备/某区域所有消息
ℹ️ 通配符只用于订阅,不能用于发布。向带通配符的 Topic 发布消息是不合法的。

三、Keep Alive:连接存活检测

3.1 Keep Alive 到底是什么

Keep Alive 可以理解为 MQTT 对长连接的一种「存活检查」约定。

它不是底层传输本身,更不是「替代 TCP」的机制;更准确地说,它是 建立在 TCP 连接之上的 MQTT 协议层保活机制

可以这样理解:

  • 客户端和 Broker 会约定一个 Keep Alive 时间,例如 60 秒
  • 在这段时间内,客户端至少要让 Broker 看到一次 MQTT 报文
  • 如果这段时间里没有正常业务报文,客户端通常会发送一个 PINGREQ
  • Broker 收到后会返回 PINGRESP
  • 如果 Broker 长时间没有等到这些报文,就会认为连接已经失效
⚠️ 注意:Keep Alive 不等于一直发业务消息,也不等于业务层自定义心跳。只要有正常 MQTT 报文来往,就不一定需要额外发送心跳包;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 遗嘱消息的工作流程

  1. 客户端在 CONNECT 报文中预先设置好遗嘱消息(Topic、Payload、QoS、Retain)
  2. 正常使用期间,遗嘱消息存放在 Broker 中
  3. Broker 检测到客户端异常断线(Keep Alive 超时)
  4. Broker 将遗嘱消息发布到对应 Topic
  5. 订阅了该 Topic 的客户端收到离线通知

5.4 遗嘱消息的典型用途

最常见的用途是设备在线状态监控

  • 连接时设置遗嘱消息:Topic device/001/status,Payload offline,retain=true
  • 连接成功后主动发布 online(retain=true)
  • 一旦异常掉线,Broker 自动发布 offline

这样其他订阅方就能实时感知设备是否在线。

5.5 遗嘱消息的几个边界

  • 遗嘱消息是异常断线才触发,正常 DISCONNECT 不会触发
  • Broker 是在检测到 Keep Alive 超时后才发送遗嘱消息,有一定延迟
  • 遗嘱消息不是瞬间触发,延迟取决于 Keep Alive 超时时间
  • 可以结合 retain=true 让新订阅者也能立即看到最新状态
⚠️ 注意:以为设备断网后遗嘱消息会立即触发是常见误区。实际上 Broker 需要等 Keep Alive 超时后才会发送,延迟可能长达数十秒甚至更久。

六、保留消息:新订阅者状态引导

6.1 保留消息是什么

普通 MQTT 消息默认只发送给「当前已订阅且在线」的客户端。

保留消息(Retained Message)则表示:

  • Broker 会为某个 Topic 保存 最后一条带 retain 标志的消息
  • 以后新的订阅者一旦订阅这个 Topic,会立刻收到这条最新消息

因此,Retain 的本质是:为某个 Topic 提供最近一次有效状态的缓存

6.2 它解决的核心问题

保留消息解决的是:新订阅者能否立刻获得当前状态,而不必等待下一次上报

例如:

  • 控制面板刚打开时,想立即知道设备当前是开还是关
  • 新接入的服务想马上知道某个传感器的最近一次读数
  • 运维面板想立刻拿到某设备最后一次上报的状态

6.3 Retain 适合什么内容

适合 Retain 的通常是「当前状态型」数据,例如:

  • device/001/status = online/offline
  • device/001/led/state = on/off
  • device/001/sensor/temperature = 26.5

这些数据的共同特点是:有「当前值」语义,新订阅者拿到最新值有意义,不需要完整历史,只需要最近状态。

6.4 Retain 不适合什么内容

一般不适合 Retain 的内容包括:

  • 历史日志
  • 事件流
  • 一次性通知
  • 一次性执行命令(例如 rebootunlockstart
⚠️ 注意:如果把「一次性命令 Topic」设置为 retained,那么新上线的客户端一订阅就可能立即收到一条旧命令,从而触发误动作。

通常建议:状态 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
  • 新订阅者立即感知状态:一订阅就拿到最近状态
  • 状态语义清晰status Topic 只承担在线状态职责

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/+/+/temperaturehome/#


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|总线特点和区别]]

官方资源