FlashDB将MQTT待发送消息落盘,设备掉电后从未完成记录继续发送

MQTT消息放在RAM队列中,设备一旦掉电,未发出的消息会全部丢失。

将消息先写入FlashDB,收到Broker ACK后再标记删除,设备重启时扫描未删除的记录继续发送,即可解决这个问题。

核心判断标准:记录存在代表消息未完成,标记删除代表处理完成。

FlashDB MQTT 断电续传流程

RAM队列的问题

MQTT发布包含两个阶段:设备将消息交给客户端,客户端等待Broker回ACK。两个阶段之间如果发生掉电,RAM中的消息无法恢复,Broker永远收不到。

因此需要一个掉电不丢失的本地队列。FlashDB TSDB模式支持追加写入、顺序遍历、重启后数据保留,适合作为这个队列的存储层。

存储设计:数据与状态分离

TSDB每条记录由消息内容和状态索引组成。状态存储在索引中,更新状态时无需重写整条消息。

业务层只需要两个状态:

"等待ACK"是发送过程中的临时阶段,不需要落盘。重启时该消息会被重新发送,存储该状态没有意义。

发送流程

每轮处理从最老的一条FDB_TSL_WRITE记录开始:

  1. 查询队头第一条待发送记录
  2. 读取记录内容,发送消息
  3. 等待ACK
  4. 收到ACK后,删除该条记录
  5. 重新查询队头,处理下一条

发送函数不直接修改数据库状态,删除作为独立操作执行。删除时从数据库重新查找第一条WRITE记录,不依赖发送函数传递的句柄或位置。这样发送和删除职责清晰,故障排查路径明确。

掉电场景与投递语义

ACK与删除是两个独立动作,中间的掉电窗口无法消除。各场景下的行为如下:

掉电时机 重启后行为
发送前 记录存在,重发
消息已发出、ACK未到达 记录存在,可能重复发送
等待ACK过程中 状态仍为WRITE,回到队头重发
ACK已到达、删除未完成 记录存在,会重复发送
删除完成后 记录不存在,不再发送

该方案选择**At-Least-Once(至少一次)**投递语义,宁可重复、不丢消息。消息去重由MQTT QoS机制或业务层消息ID处理。

边界与扩展