3、MQTT核心原理(下):保留消息、遗嘱消息与会话机制
上一章我们聊了MQTT的发布/订阅模型和QoS等级。今天继续深入,把剩下的三个核心机制讲透——保留消息、遗嘱消息和会话机制。这三个东西在实际项目中用得非常频繁,尤其是做物联网设备管理的时候,几乎天天打交道。
3.1 保留消息(Retained Message)
先问一个问题:如果一个新设备上线,它怎么知道当前系统的状态?比如温度传感器刚通电,它想知道最近一次的温度值是多少。如果没有保留消息,它只能等下一次温度发布才能拿到数据。这显然不够实时。
保留消息就是干这个用的。
当发布者发送消息时,把 Retained 标志位设为 true,Broker 就会把这条消息存下来。以后任何客户端订阅这个主题,Broker 会立刻把最后一条保留消息推送给它。
关键特性:
- 每个主题只能保留一条消息
- 新的保留消息会覆盖旧的
- 发送空消息体(payload 长度为 0)且 Retained=true 可以清除保留消息
- 保留消息与发布者无关,Broker 重启后仍然存在(取决于 Broker 实现)
我个人习惯在设备状态上报的场景里用保留消息。比如智能灯控系统,每个灯定期上报亮度、色温,新加入的网关订阅后立刻拿到当前状态,不用等一轮轮询。
使用建议:
保留消息适合存储设备的最终状态,不适合存储日志或时序数据。日志数据量大,保留消息只存一条,存日志没意义。
3.2 遗嘱消息(Will Message)
物联网设备经常掉线——电池没电、网络断连、设备死机。怎么让其他设备知道这个设备挂了?遗嘱消息就是干这个的。
客户端在连接 Broker 时,可以在 CONNECT 报文里带上遗嘱消息。如果客户端非正常断开(比如网络断开、心跳超时),Broker 就会帮它发布这条遗嘱消息。
注意:
客户端主动发送 DISCONNECT 报文正常断开,Broker 不会发布遗嘱消息。只有非正常断开才会触发。
我在一个智能门锁项目里用过这个机制。每个门锁连接时设置遗嘱消息:
# 伪代码示例
client.connect(
host="broker.example.com",
port=1883,
keepalive=60,
will_topic="device/lock/status",
will_payload="offline",
will_qos=1,
will_retain=True
)
门锁正常工作时,定期发布 "online" 到同一个主题。一旦掉线,Broker 自动发布 "offline"。后台管理系统收到后立刻告警,通知物业去检修。
遗嘱消息的 QoS 和 Retain 标志可以单独设置。我建议遗嘱消息的 QoS 至少设为 1,确保消息能送达。Retain 设为 true 也不错,这样新订阅者也能知道设备已经离线。
3.3 会话机制(Clean Session)
会话机制是 MQTT 里最容易让人迷糊的概念。说白了,它决定了 Broker 要不要帮你记住订阅关系和离线消息。
客户端连接时,CONNECT 报文里有一个 Clean Session 标志位:
| Clean Session | 含义 | 适用场景 |
|---|---|---|
| true(1) | 每次连接都是全新会话,不恢复之前的订阅和离线消息 | 临时客户端、数据订阅端 |
| false(0) | 持久会话,Broker 保存订阅关系和离线消息 | 设备端、需要离线消息的客户端 |
举个例子。一个温度传感器每隔 10 秒上报一次数据,但后台监控系统偶尔断网。如果传感器用 Clean Session=false 连接,后台断网期间的消息会被 Broker 缓存起来,等后台重新连上后再推送。
持久会话的存储内容:
- 客户端的订阅关系
- QoS 1 和 QoS 2 的离线消息
- 客户端离线期间错过的消息
这里有个坑。我曾经在一个项目里,所有设备都用 Clean Session=false,结果 Broker 内存暴涨。为什么?因为设备数量太多,每个设备都缓存了大量离线消息。后来我调整了策略:
- 数据上报类设备:用 Clean Session=true,丢了就丢了,反正下一轮数据马上来
- 控制指令类设备:用 Clean Session=false,确保指令不丢失
- 后台监控系统:用 Clean Session=false,但设置合理的消息过期时间
3.4 三者协同工作
这三个机制不是孤立的。在实际系统中,它们经常配合使用。我画了一张图,帮你理清它们的关系:
3.5 避坑指南
做 MQTT 开发这几年,我踩过不少坑。挑几个典型的说说:
坑一:保留消息的“幽灵”问题
我曾经在一个项目里,设备更换后旧设备的保留消息还在。新设备上线后,订阅者收到的是旧状态。解决方案:设备上线时先发送空保留消息清除旧数据,再发布新状态。
坑二:遗嘱消息的 QoS 陷阱
遗嘱消息的 QoS 如果设为 0,Broker 可能丢消息。我建议至少用 QoS 1。另外,遗嘱消息的 topic 最好和正常状态上报的 topic 分开,避免逻辑混乱。
坑三:持久会话的内存爆炸
大量设备用 Clean Session=false,且 QoS 设为 2,Broker 内存会迅速膨胀。我建议:
- 控制持久会话的数量
- 设置消息过期时间(Message Expiry Interval)
- 定期清理无效会话
3.6 小结
保留消息、遗嘱消息和会话机制,是 MQTT 协议里最实用的三个特性。保留消息让新订阅者快速获取状态,遗嘱消息让系统感知设备离线,会话机制保证消息不丢。三者配合,基本能覆盖物联网通信中 90% 的可靠性需求。