Files
full-stack-fastapi/docs/superpowers/specs/2026-03-17-mqtt-raw-ingestion-design.md
2026-03-17 11:49:31 +08:00

7.6 KiB
Raw Blame History

MQTT 原始数据接收与保存设计(双阶段)

1. 背景与目标

在现有 full-stack-fastapi-template 工程基础上,新增 MQTT 采集能力,用于接收终端上报的 GNSS 数据并可靠保存,支持后续:

  • 前端秒级实时展示(设备看板 + 逐条观测流)
  • 按天/时间段下载原始数据供外部软件分析

本设计阶段仅实现“接收 + 保存 + 可解析落库链路”,不实现前端页面。

2. 已确认约束

  • 部署环境:腾讯云,同机已有 EMQX
  • Broker 地址:101.35.119.226:1883
  • 鉴权方式:用户名/密码
  • Topicterminal/{device_id}/raw
  • device_id:直接从 topic 中提取
  • 上报形态:每 1~10 秒一条消息,每条消息为“完整帧/完整语句集合”
  • 负载内容:可能为 NMEA only、RTCM3.2 only 或二者混合
  • NMEA 类型GGA / RMC / GSV / GSA / GST
  • RTCM本期仅做“消息类型级别摘要”
  • 存储策略90 天热数据 + 本地冷归档
  • 下载要求:下载内容必须是原始 MQTT payload 字节流,不附加元信息
  • 下载文件后缀:.txt(内容可包含二进制字节)

3. 架构方案与结论

3.1 备选方案

  1. FastAPI 内嵌 MQTT 客户端
  2. DB 队列双阶段(独立 ingestor + parser worker
  3. 外部消息队列三阶段

3.2 选型结论

采用 方案 2DB 队列双阶段

理由:

  • 避免 FastAPI 多 worker 下重复订阅/重复入库风险
  • 接收链路与解析链路解耦,稳定性更高
  • 原始数据先落库,可重试、可回放、可追溯
  • 为后续实时展示和下载提供稳定基础

4. 组件职责

4.1 mqtt-ingestor采集层

  • 常驻独立进程,连接 EMQX 并订阅 terminal/+/raw
  • 回调中仅执行轻量逻辑:
    • 提取 device_id
    • 记录 topic/qos/retain/received_at
    • 原样保存 payload_bytes
    • 计算 payload_sha256payload_size
    • 插入 terminal_message_rawparse_status='pending'
  • 不执行 NMEA/RTCM 深解析

4.2 parser-worker解析层

  • 轮询 terminal_message_raw 中待解析记录
  • 解析 NMEA + RTCM3.2 摘要
  • 写入事件表 terminal_observation_event
  • Upsert 最新状态表 terminal_device_state
  • 失败可重试,错误可观测,不丢原始数据

4.3 backend API服务层

  • 本期不新增前端页面
  • 后续基于 event + state + archive manifest 增加秒级展示与下载接口

5. 数据模型设计

5.1 terminal_message_raw热数据原始表

用途:保存原始消息,作为解析队列与下载来源。

建议字段:

  • id (uuid, pk)
  • device_id (text, index)
  • topic (text)
  • received_at (timestamptz, index)
  • payload_bytes (bytea)
  • payload_size (int)
  • payload_sha256 (char(64), index)
  • qos (smallint)
  • retain (bool)
  • parse_status (pending|processing|succeeded|failed, index)
  • parse_attempts (int, default 0)
  • next_retry_at (timestamptz, nullable, index)
  • parse_error (text, nullable)
  • created_at (timestamptz)

5.2 terminal_observation_event解析事件流

用途:支撑逐条观测流展示与历史分析。

建议字段:

  • id (uuid, pk)
  • raw_id (uuid, fk -> terminal_message_raw.id, index)
  • device_id (text, index)
  • event_time (timestamptz, index)
  • event_type (text) 示例:nmea_gga / nmea_rmc / nmea_gsv / nmea_gsa / nmea_gst / rtcm_summary
  • payload_json (jsonb)
  • created_at (timestamptz)

5.3 terminal_device_state设备最新状态

用途:支撑前端设备看板秒级刷新。

建议字段:

  • device_id (text, pk)
  • last_event_time (timestamptz)
  • fix_status (text)
  • lat / lon (double precision)
  • alt_m (double precision)
  • satellites_used (int)
  • satellite_signal_summary (jsonb)
  • rtcm_type_summary (jsonb)
  • updated_at (timestamptz)

5.4 terminal_raw_archive_manifest冷归档索引

用途:记录本地归档文件与时间范围映射。

建议字段:

  • id (uuid, pk)
  • device_id (text, index)
  • range_start / range_end (timestamptz, index)
  • file_path (text, unique)
  • file_size (bigint)
  • sha256 (char(64))
  • record_count (bigint)
  • created_at (timestamptz)

6. 双阶段流水线细节

6.1 入站ingestor

  • MQTT 建议参数:clean_session=False、固定 client_id、QoS=1
  • 批量入库建议:每 100 条或 200ms flush 一次事务
  • 目标:尽快持久化原始字节,避免解析阻塞接收

6.2 解析parser-worker

批处理取数:

  • 条件:parse_status='pending'next_retry_at 到期
  • 顺序:ORDER BY received_at
  • 锁:FOR UPDATE SKIP LOCKED
  • 批量:LIMIT 100~500

处理规则:

  • 成功:parse_status='succeeded'
  • 失败:parse_status='failed' + parse_attempts+1 + parse_error
  • 重试:退避后设置 next_retry_at

幂等建议:

  • 事件表增加防重策略(如 raw_id + event_type + event_time 约束或哈希去重)
  • terminal_device_state 用 upsert仅更新更晚 event_time

7. 解析策略(本期)

7.1 NMEA

  • 解析 GGA/RMC/GSV/GSA/GST
  • 每条语句产出一条 terminal_observation_event

7.2 RTCM3.2

  • 仅提取消息类型摘要(如 1005/1077/1087
  • 每条 raw payload 产出一条 rtcm_summary 事件,包含类型计数与最近出现时间

7.3 混合负载

  • 单 payload 中可同时包含 NMEA 与 RTCM 片段
  • 解析器按字节扫描拆分,分别产出事件
  • 部分片段解析失败不影响其余片段;原始 payload 永远保留

8. 热数据与本地冷归档

8.1 热数据

  • terminal_message_raw 保留 90 天

8.2 本地冷归档

  • 定时任务离峰执行
  • 归档粒度:device_id + 日(必要时可按小时分片)
  • 路径示例:/data/archive/gnss/<device_id>/YYYY-MM-DD/
  • 文件内容:仅原始 payload 字节流拼接(无额外元信息)
  • 文件名示例:device_<id>_<start>_<end>.txt

8.3 校验与清理

  • 写入 manifest时间范围、条数、sha256、文件路径
  • 校验通过后,清理超过 90 天热数据

9. 下载行为约束(后续接口实现遵循)

  • 下载内容必须与原始 MQTT payload 字节一致
  • 近 90 天:从热表按时间顺序拼接输出
  • 超过 90 天:从本地归档读取输出
  • 跨热/冷边界:按时间顺序拼接两段数据

10. 运维与可观测性

必须监控:

  • ingestor连接状态、入站速率、入库失败
  • parserpending backlog、解析成功率、平均延迟、失败重试
  • archive每日归档成功/失败、磁盘余量

建议告警:

  • pending 队列持续增长
  • parser 失败率超阈值
  • 归档目录磁盘不足

11. 配置项建议(.env

  • MQTT_HOST=101.35.119.226
  • MQTT_PORT=1883
  • MQTT_USERNAME=...
  • MQTT_PASSWORD=...
  • MQTT_TOPIC_PATTERN=terminal/+/raw
  • MQTT_CLIENT_ID=spatialhub-mqtt-ingestor
  • RAW_HOT_RETENTION_DAYS=90
  • RAW_ARCHIVE_BASE_DIR=/data/archive/gnss
  • PARSER_BATCH_SIZE=200
  • PARSER_POLL_INTERVAL_MS=300

12. 风险与后续演进

  • 使用 .txt 扩展名存放二进制流可能被文本工具误打开;但不影响下游软件按字节读取
  • 当设备量增长时,可水平扩展 parser-worker依赖 SKIP LOCKED
  • 后续实时能力:新增 SSE/WebSocket 推送层,数据源直接复用 event/state

13. 验收标准(本期)

  • 能稳定接收 terminal/{device_id}/raw 消息并原样入库
  • 混合 payload 可提取 NMEA 事件和 RTCM 类型摘要
  • 设备最新状态可持续更新
  • 支持 90 天热数据保留与本地冷归档落盘
  • 能按时间段导出原始数据(文件后缀 .txt,内容无附加字段)