2. MQTT 基础回顾
Sparkplug 并不是一套全新的通信协议,它建立在 MQTT 3.1.1 / 5.0 之上,通过约定主题命名空间、载荷编码和状态机,把“通用消息总线”约束成“工业设备状态总线”。因此,理解 Sparkplug 的设备状态管理,必须先回到 MQTT 的五个核心机制:发布/订阅模型、主题、QoS、保留消息和遗嘱消息。它们分别解决了“谁和谁通信”“消息发到哪里”“消息可靠到什么程度”“新订阅者如何立刻拿到状态”“设备掉线如何被感知”这五个问题。
一、发布/订阅模型:解耦是状态管理的前提
MQTT 采用发布/订阅(Pub/Sub)模型,而不是 HTTP 那样的请求/响应模型。发布者(Publisher)把消息发送给 Broker,订阅者(Subscriber)向 Broker 订阅自己关心的主题。发布者与订阅者彼此不知道对方的存在,也不需要同时在线。这种解耦带来三个直接好处:
- 空间解耦:设备只连 Broker,不需要知道 SCADA、MES、云平台各自的地址。
- 时间解耦:设备上报时,上层应用可以不在线;应用上线后可通过保留消息补齐最新状态。
- 一对多分发:一条设备状态消息可同时被多个订阅者消费,无需设备重复发送。
在 Sparkplug 中,边缘节点(Edge Node)发布自己的状态,主应用(Primary Host Application)订阅整个 Sparkplug 命名空间。Broker 成为唯一的状态汇聚点,这正是“设备状态管理”能够集中化的基础。
二、主题:Sparkplug 的命名空间骨架
主题(Topic)是 MQTT 的消息路由地址,使用斜杠分层,支持 + 单层通配和 # 多层通配。普通 MQTT 主题可以任意命名,而 Sparkplug 强制规定了命名空间:
spBv1.0/<group_id>/<message_type>/<edge_node_id>/[<device_id>]
示例:
spBv1.0/LineA/NBIRTH/PLC01
spBv1.0/LineA/NDATA/PLC01
spBv1.0/LineA/DDATA/PLC01/Sensor07
spBv1.0/LineA/NDEATH/PLC01
其中 spBv1.0 是 Sparkplug B 版本命名空间,group_id 用于逻辑分组,message_type 决定消息语义(NBIRTH、NDATA、NDEATH、DBIRTH、DDATA、DDEATH、STATE 等)。主题不再只是“地址”,而是承载了消息类型和层级关系,使订阅者可以只订阅 spBv1.0/LineA/# 就获得整条产线的状态流。
三、QoS:状态消息的可靠等级
MQTT 定义三个服务质量等级,决定消息投递的保证程度:
| QoS | 语义 | 交互次数 | 典型用途 |
|---|---|---|---|
| 0 | 最多一次,可能丢失 | PUBLISH | 高频遥测、可容忍丢点 |
| 1 | 至少一次,可能重复 | PUBLISH / PUBACK | 设备状态、告警 |
| 2 | 恰好一次,开销最大 | 四次握手 | 计费、关键指令 |
Sparkplug 规范建议状态类消息(NBIRTH、NDEATH、DBIRTH、DDEATH)使用 QoS 1,数据类消息(NDATA、DDATA)也通常使用 QoS 1。原因是状态管理更怕“丢”而不是“重”:重复消息可以通过 bdSeq、seq 序号去重,但丢失的 BIRTH 或 DEATH 会导致上层状态长期错误。
四、保留消息与遗嘱消息:状态快照与掉线感知
保留消息(Retained Message):发布时设置 retain 标志,Broker 会为该主题保存最后一条保留消息。新订阅者一旦订阅匹配主题,立即收到这条消息,无需等待下一次发布。Sparkplug 利用这一机制,让主应用启动后能立刻获得各节点的最新 BIRTH 证书,而不必轮询或等待设备重发。
遗嘱消息(Will Message):客户端在 CONNECT 时向 Broker 注册遗嘱。当客户端异常断开(网络中断、掉电、未发送 DISCONNECT),Broker 自动代其发布遗嘱消息。Sparkplug 规定遗嘱主题为 spBv1.0/<group_id>/NDEATH/<edge_node_id>,载荷中包含 bdSeq 和时间戳。主应用订阅 NDEATH 后,即可在秒级内感知节点离线,并把该节点下所有设备标记为不可用。
五、案例:一条产线的状态建立与掉线感知
假设产线 LineA 上有一台 PLC01,其下挂 Sensor07。系统启动后的典型时序如下:
- PLC01 连接 Broker,注册遗嘱主题
spBv1.0/LineA/NDEATH/PLC01,载荷含 bdSeq=0。 - PLC01 发布
spBv1.0/LineA/NBIRTH/PLC01,QoS 1,retain=true,列出所有指标与别名。 - Sensor07 发布
spBv1.0/LineA/DBIRTH/PLC01/Sensor07,声明自身指标。 - 主应用订阅
spBv1.0/LineA/#,立即收到保留的 NBIRTH 与 DBIRTH,建立完整设备树。 - 运行期间,PLC01 周期发布 NDATA,Sensor07 发布 DDATA,QoS 1,seq 递增。
- 若 PLC01 掉电,Broker 检测到 TCP 断开,代发 NDEATH;主应用收到后把 PLC01 及其下所有设备标记为离线。
六、常见误区与复盘
- 误区一:认为 QoS 2 最安全,全部用 QoS 2。QoS 2 四次握手会显著增加延迟和 Broker 负担,状态消息用 QoS 1 加序号去重更合适。
- 误区二:忘记设置 retain。NBIRTH 不保留,主应用重启后将长时间处于“未知状态”,直到设备下一次 BIRTH。
- 误区三:遗嘱主题写错或未注册。遗嘱必须在 CONNECT 报文里声明,连接后再设置无效;主题不符合 Sparkplug 命名空间,主应用无法识别。
- 误区四:用普通 DISCONNECT 断开。正常断开时 Broker 不会发布遗嘱,若业务上需要“主动下线”语义,应显式发布 NDEATH 或依赖 STATE 主题。
- 误区五:忽略 bdSeq 与 seq。bdSeq 用于区分同一次会话的 BIRTH/DEATH 配对,seq 用于检测丢包与乱序,二者是状态一致性的关键参数。