QoS 选择与会话恢复:重复、丢失、乱序的取舍
采用 FOC 驱动方式、额定 25A(峰值 40A)的 AQMD6030BLS-E2 这类设备,网络抖动是常态。QoS 选错会带来两类相反的故障:选 0 丢关键指令,选 1 重复执行。二者不能靠"感觉"选,要按消息语义分类。
一、按语义分类,而不是全局统一
| 消息类型 | 建议 QoS | 理由 | 失败后果 |
|---|---|---|---|
| 周期性遥测(可丢可覆盖) | 0 | 丢一帧不影响趋势,省带宽与限流额度 | 数据缺口 |
| 事件/故障上报(不可丢) | 1 | 至少一次,配合幂等去重 | 漏报故障 |
| 下行控制指令 | 1 | 必须到达,且必须幂等 | 指令丢失或重复执行 |
| 状态同步(取最新) | 1 + 版本号 | 允许重复,靠版本合并 | 状态回退 |
限流额度也要一起算:平台对单设备上报有 QoS 维度的差异(QoS0 与 QoS1 的每秒条数上限不同,具体数值以官方文档当前版本为准)。同一业务量下全用 QoS1 会显著压缩可用额度,因此遥测用 0、事件用 1 是常见折中。
二、QoS1 的三个副作用
重复。 QoS1 是"至少一次",重传必然可能重复。所有 QoS1 消息必须带唯一 ID(设备序列号 + 单调序号),接收端做去重窗口。
乱序。 重传会让后发的消息先到。消费端不能假设到达顺序等于发送顺序,需要按序号排序或按时间戳合并;对状态类数据用版本号"只前进不回退"。
会话恢复的隐藏依赖。 若使用持久会话(clean session=false),设备离线期间的 QoS1 消息会由平台暂存并在重连后下发,这依赖会话未被过期清理。会话缓存有容量与时长上限(以官方文档为准),长时间离线仍会丢,因此关键指令不能只依赖会话恢复,必须配合"重连后主动拉取期望状态"(与设备影子联动)。
/* QoS1 去重:环形位图记录最近 N 个序号,重复则丢弃 */
#define DEDUP_N 256
typedef struct {
uint32_t base; uint8_t seen[DEDUP_N]; } dedup_t;
int dup_check(dedup_t *d, uint32_t seq)
{
if (seq < d->base) return 1; /* 过旧,视为重复 */
uint32_t off = seq - d->base;
if (off >= DEDUP_N) {
/* 窗口前移 */
uint32_t move = off - DEDUP_N + 1;
for (uint32_t i = 0; i < DEDUP_N - move; i++) d->seen[i] = d->seen[i + move];
for (uint32_t i = DEDUP_N - move; i < DEDUP_N; i++) d->seen[i] = 0;
d->base += move; off = seq - d->base;
}
if (d->seen[off]) return 1;
d->seen[off] = 1;
return 0;
}
三、上行侧的补位设计
QoS0 遥测丢帧后,云端会看到时间断点。做法是消息里带"采样序号 + 设备时间戳",云端按序号检出缺口并标记为缺失(而不是插值伪造);对可靠性要求高的遥测改用 QoS1 + 合并上报,把条数压下来换取不丢。
四、验证方法
一是丢包注入:用网络仿真丢 5%/30% 上行包,核对 QoS0 遥测缺口率与 QoS1 事件的实际到达率;二是重复注入:同一 QoS1 消息重发 5 次,确认业务只执行一次;三是长离线测试:离线 6h/24h 后重连,确认会话恢复的实际效果与"主动拉取期望状态"的兜底是否生效。
五、小结
QoS 按消息语义分类:可覆盖的遥测用 0,事件与指令用 1 并强制幂等去重。QoS1 带来重复与乱序,需唯一 ID + 序号窗口 + 版本合并;持久会话能缓解但不能替代重连后主动拉取期望状态。用丢包、重复、长离线三类注入测试闭环。