客户在管理后台点开一台机器人,希望看到的是"它现在在哪、电量多少、是不是真的在既定展位表演"。机器人租赁系统的这个"设备状态实时可见",背后是整个 IoT 设备监控链路的架构功夫:设备影子、消息链路、状态机、时序存储,环环相扣。这篇把我们 IoT 接入层的设计逐层拆开讲,重点讲为什么这么设计、不这么设计会怎样。


一、设备影子的核心模型:期望状态 vs 上报状态

在这里插入图片描述

IoT 接入层第一个要解决的不是性能,是设备网络不可靠时的状态表达。机器人在展馆、商场里跑,Wi-Fi/4G 断连是家常便饭。如果业务系统直接依赖"设备实时在线才算有状态",断网期间订单履约判断、远程指令下发全都会失灵。

解法是借鉴 AWS IoT Shadow 的设备影子(Device Shadow)模型:为每台设备在云端维护一份 JSON 文档,分成两个区:

{
  "reported": {
    "battery": 86,
    "location": { "lat": 23.1291, "lng": 113.2644 },
    "speedMode": 2,
    "taskStatus": "PERFORMING",
    "reportedAt": "2026-08-21T10:23:00+08:00"
  },
  "desired": {
    "speedMode": 1,
    "geoFence": { "type": "circle", "center": [23.1291, 113.2644], "radius": 200 }
  },
  "version": 47
}
  • reported(上报状态):设备最后一次上报的事实,断网前也不会丢;
  • desired(期望状态):业务侧想让它到达的状态(降速、限定围栏);
  • version:乐观锁版本号,防止指令乱序覆盖。

设备上线后的第一件事是拉取影子,对比 desired 和 reported 的差异,把差异自动转成待执行指令——断网期间下发的"降速到 1 档",在设备恢复联网后 3 秒内自动生效。业务层永远只读写影子,不关心设备此刻在不在线。这个模型让"远程控制"从"祈祷设备在线"变成"写期望值 + 等确认",可靠性完全是另一个量级。

影子的存储选 Redis(JSON 序列化 + 按 deviceId 分 key),写入频率受控:遥测明细进时序库,只有状态字段变化才更新影子,避免每秒定位上报把 Redis 打爆。

二、数据链路:MQTT → 规则引擎 → 时序库 → Redis,四段各司其职

在这里插入图片描述

完整的数据链路是四段流水线,每段的职责严格分开:

设备(MQTT QoS0/1)
  → EMQX(接入认证、ACL、会话管理)
    → 规则引擎(解析 payload、按 Topic 分流)
      ├─ 遥测数据 → TDengine(明细存储、历史查询)
      ├─ 状态变化 → Redis 影子更新(供业务实时读)
      └─ 异常事件 → Kafka → 告警服务(电子围栏、低电量、离线)

设计上的三条关键纪律:

  1. 分流在规则引擎做配置,不在 Java 写 if。EMQX 规则引擎按 Topic + payload 条件路由,新增一种遥测类型多数情况只改一条规则,不发版;
  2. 写入时序库走批量。规则引擎把遥测写入 Kafka 做缓冲,TDengine 消费端按 500 条或 1 秒批量写入,实测把写入吞吐从单条模式的 8000 行/s 提到 5 万行/s 以上;
  3. 业务读路径永远不碰时序库。实时状态读 Redis 影子(P99 < 5ms),历史轨迹和报表才查 TDengine。曾有同事想直接从时序库聚合"当前电量",大查询把时序库 CPU 打满拖累了轨迹查询——读写分离的边界必须划死。

三、设备业务状态机:心跳超时判定与状态流转

在这里插入图片描述

设备有两套状态,不要混淆:通信状态(ONLINE/OFFLINE,由 MQTT 会话决定)和业务状态(可租/租出/维修,由订单和工单决定)。很多初期设计把两者混在一个字段,结果"设备离线了要不要把它标为不可租"这种问题永远吵不清。

业务状态机定义:

IDLE(可租) ⇄ LEASED(租出)
  ↓            ↓
REPAIR(维修) → SCRAP(报废)

通信状态作为独立维度,通过心跳超时判定维护:EMQX 的会话上下线事件触发通信状态更新,同时兜底一条"超过 90 秒无任何心跳/遥测即判定离线"的扫描任务——为什么需要兜底?因为 EMQX 集群重启或会话事件丢失时,上下线事件并不绝对可靠,交易系统不能依赖单一信号源。

状态机流转的约束用代码强制:状态变更走 deviceStateMachine.transition(deviceId, target, event),非法流转直接抛异常(例如 REPAIR 状态设备不允许被下单——下单链路校验状态机而非裸查字段)。围栏越界、低电量这类事件会触发状态机动作:越界事件推送告警给商家调度员,连续离线超过 30 分钟且处于 LEASED 状态的设备自动创建"失联核查"工单。

判定阈值的经验值:心跳周期 30 秒,离线判定 90 秒(3 个周期),失联工单 30 分钟。阈值放配置中心按机型可调——不同机型的心跳周期不同,写死一个全局值迟早出问题。

四、存储选型:时序库 vs MySQL 分区表,算一笔成本账

设备遥测数据存哪里,评审时吵得最凶。把我们的对比摆出来(按 2000 台设备、每台 30 秒一条、每天约 576 万行估):

维度TDengineInfluxDBMySQL 分区表(按月)
存储成本10:1+ 压缩,月增约 2-3GB压缩好但内存开销大月增 25-40GB
写入吞吐批量写轻松 5 万行/s+8 千-1 万行/s 见顶
聚合查询(90 天轨迹)亚秒级亚秒级分钟级,需预聚合
运维成本单 binary,学习曲线缓需适应 Flux/InfluxQL团队零学习成本
生态绑定国产、SQL 方言开源版已转向云服务通用

结论:设备量过千、保留周期超过 90 天时,MySQL 分区表在存储成本和聚合性能上都会先垮;InfluxDB 开源版的方向让人顾虑长期依赖;TDengine 的 SQL 兼容 + 高压缩 + 低运维在我们场景最合适。但公平地说:设备量 500 台以内、数据保留 30 天,MySQL 按月分区 + 定期归档完全够用,为一个小区别多养一个存储组件不划算——我们第一版就是 MySQL 分区表,跑到 800 台设备时才迁移,因为模型设计时遥测表结构保持了"宽表扁平 + 标签列"风格,迁移成本只有重放脚本的三天时间。

五、总结:IoT 接入层设计清单

  • 设备影子是断网时代的正确答案:reported + desired + version,业务读写永远对着影子;
  • 数据链路四段分工:EMQX 管接入、规则引擎管分流、时序库管明细、Redis 管实时读,业务读路径不碰时序库;
  • 通信状态与业务状态双轨制,心跳超时要有扫描任务兜底,不依赖单一信号源;
  • 状态机非法流转代码级强校验,异常事件自动触发告警与工单;
  • 存储选型按设备量分阶段:500 台内 MySQL 分区表,过千上时序库,表结构提前按可迁移风格设计。

我们在设备租赁与资产管理类系统的定制开发上有多年落地经验,设备影子、围栏告警、状态机这些 IoT 模块都在真实租赁业务里长期运行。如果你也在规划机器人租赁或类似平台,欢迎交流,IoT 接入层的方案可以按你们的设备规模直接套。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐