MQTT 保留消息完整指南
一、保留消息是什么
保留消息是 MQTT 的一种机制。发布者在发布消息时,如果设置了 retain = true,Broker 就会为这个主题保存这条消息。之后任何新订阅这个主题的客户端,都会立刻收到这条保留消息。
它的核心作用是:让新订阅者不用等下一次发布,就能立刻拿到该主题的最新状态。
二、保留消息的核心规则
| 规则 | 说明 |
|---|---|
| 只存最后一条 | 同一个主题下,新的保留消息会覆盖旧的 |
| 新订阅者立刻收到 | 订阅时立即推送,不用等下一次发布 |
| 与订阅者无关 | 不管有没有人订阅,保留消息都一直存在 |
| 谁都能删 | 向该主题发布一条空 Payload 的保留消息,即可删除 |
| 由发布者决定 | 发布时设 retain = true 才会被保留 |
三、完整逻辑
发布者发布 retain=true 的消息 -> Broker 把这条消息存起来(每个主题只存最后一条) -> 新订阅者上线,订阅该主题 -> Broker 立刻把保留消息推给它。
四、触发条件:SUBSCRIBE,不是连接
保留消息的触发条件是 SUBSCRIBE(订阅动作),而不是连接本身。
根据 MQTT 3.1.1 规范 [MQTT-3.8.4-3]:如果服务端收到一个 SUBSCRIBE 报文,其主题过滤器与现有订阅的主题过滤器相同,它必须用新订阅完全替换现有订阅,并且必须重新发送匹配该主题过滤器的任何现有保留消息。
这意味着:即使客户端已经订阅了该主题,只要它再次发起 SUBSCRIBE,Broker 就会重新推送保留消息。
| 场景 | 会收到保留消息吗 | 说明 |
|---|---|---|
| 全新的订阅者(第一次订阅该主题) | 会 | 订阅关系建立,立刻推送保留消息 |
| 已订阅者重新上线,重新发起 SUBSCRIBE | 会 | 重新订阅会触发保留消息重新推送 |
| 已订阅者重新上线,没有发起 SUBSCRIBE | 不会 | 没有新的订阅动作,不会重发保留消息 |
五、结合 Clean Session 的实际情况
Clean Session 管的是“会话状态是否保留”,保留消息管的是“订阅动作是否触发推送”。两者作用不同,不能简单地说“Clean Session=false 就不会收到保留消息”。
| Clean Session | 重连后是否重新订阅 | 会收到保留消息吗 |
|---|---|---|
| false | 恢复原订阅关系,通常不重新 SUBSCRIBE | 通常不会,因为没有新的 SUBSCRIBE 动作 |
| false | 客户端代码主动重新 SUBSCRIBE | 会,重新订阅会触发保留消息重新推送 |
| true | 旧会话被清空,必须重新 SUBSCRIBE | 会 |
关键点:不是 Clean Session=false 本身导致收不到保留消息,而是因为客户端没有发起新的 SUBSCRIBE 动作。如果客户端在重连后主动重新订阅了主题,保留消息依然会被推送。
六、为什么“会话过期时间=0”也能收到消息
在 MQTTX 里,如果你把“会话过期时间”设为 0,理论上 Broker 在你断开后会立即销毁会话,离线消息不可能被保留。
但你依然收到了消息,原因通常是:
- 你收到的是保留消息,不是离线消息。
- 保留消息的触发条件是 SUBSCRIBE,只要你重连后重新订阅了主题,Broker 就会把该主题的最后一条保留消息推给你。
- 保留消息和离线消息是两套独立的机制,保留消息不依赖持久会话。
七、保留消息 vs 遗嘱消息
| 对比项 | 保留消息 | 遗嘱消息 |
|---|---|---|
| 谁设置 | 发布者发布时设置 | 客户端连接时设置 |
| 谁发布 | 发布者自己发布 | Broker 代为发布 |
| 触发条件 | 有客户端发起 SUBSCRIBE | 客户端异常断开 |
| 是否一次性 | 否,一直保留到被覆盖或删除 | 是,发布后从会话中删除 |
| 能否被新订阅者收到 | 能,只要没被删除 | 只有设置了 Retain 才能 |
八、保留消息 vs QoS 1/2 离线消息
| 对比项 | 保留消息 | QoS 1/2 离线消息 |
|---|---|---|
| 保留几条 | 每个主题只保留最后一条 | 离线期间的所有 QoS 1/2 消息 |
| 触发条件 | SUBSCRIBE | 订阅方重连后恢复会话 |
| 前提条件 | 发布时设 retain=true | 订阅方使用持久会话,订阅 QoS > 0 |
| 适用场景 | 设备状态、最新配置 | 关键指令、需要补发的消息 |
九、MQTT 5.0 会话过期间隔的四种情况
MQTT 5.0 引入了 Session Expiry Interval,它和 Clean Start 组合,决定了会话在断开后的保留行为[citation:4]:
| Clean Start | 会话过期间隔 | 断开后行为 |
|---|---|---|
| true | 0 | 立即销毁会话 |
| true | 无穷大 | 立即销毁会话(cleanStart=true 优先) |
| false | > 0 | 保留会话一段时间,期间重连可恢复,超时则销毁 |
| false | 无穷大 | 永久保留会话,重连可恢复 |
十、一句话总结
保留消息由发布者在发布时设置 retain = true,Broker 为每个主题存下最后一条。触发条件是 SUBSCRIBE,不是连接。Clean Session=false 时重连,如果客户端没有发起新的 SUBSCRIBE,通常不会收到保留消息;但如果客户端主动重新订阅,保留消息依然会被推送。会话过期时间=0 时收到的消息,大概率是保留消息,不是离线消息。