
摘要搭工业 IoT 平台大家的注意力都在后半程存储怎么分区、流计算怎么调优、告警怎么治理。但每个平台真正的第一道坎在前半程——设备数据怎么进来。现实是新上一批设备三家厂商三种协议。A 家走 MQTT主动往主题上推 JSONB 家是老 PLC只给 Modbus你得按寄存器地址轮询C 家上了 OPC UA能订阅但节点命名一套一套的。把三种协议接上不难难的是接好——时间戳谁打、值怎么换算、坏数据怎么标、断线了怎么补这些第一公里的脏活决定后面所有分析的上限。本文把接入层拆开讲三类协议的采集语义差异、点表作为接入的元数据中心、统一汇聚流表的设计含质量位、死区上报策略、工程量转换放在哪一层做、断线补采的语义。核心思路一句话协议差异在汇聚层归一成时间戳 测点 值 质量四元组转换和清洗全部入库后做采集端只做尽量少的事。一、三类协议三种采集语义先把三类主流协议的脾气摸清。它们不只是报文格式不同采集语义完全不同——谁来发起通信、数据长什么样、带不带类型和时间戳全都不同。1.1 MQTTpush 型消息MQTT 是发布/订阅模型设备或网关作为客户端把数据主动推到 broker 的主题上平台订阅主题收数据。通信由设备侧发起平台被动收数据形态是消息payload 通常是 JSON 或二进制测点名和值都藏在 payload 里需要按约定解析没有时间戳语义除非 payload 里自己带——不带的话只能用到达时刻而到达时刻经过网络和 broker 排队已经不等于采集时刻没有质量语义设备掉了就是消息断了靠多久没收到来感知1.2 Modbuspull 型轮询Modbus 是主从模型平台主站按寄存器地址主动问设备从站回答寄存器里的 16 位整数。通信由平台侧发起你不去问它永远不说数据形态是寄存器一个地址对应一个 16 位原始值没有测点名、没有单位、没有小数点——4201 可能是 42.01°C系数是你和设备厂商约定的事没有时间戳——轮询发出的时刻就是你能拿到的最好时间戳没有质量语义读失败超时/异常码就是这次轮询空手而归轮询周期决定数据密度和链路负载的平衡问得太勤链路忙问得太疏丢变化1.3 OPC UA带模型的订阅OPC UA 比 Modbus重得多设备暴露一个地址空间每个测点是有类型的节点节点 ID 数据类型 工程单位支持订阅监视值变化时主动通知。通信可以订阅变化推送也可以轮询数据形态是类型化节点值自带类型节点自带工程单位语义相对完整自带时间戳和质量位——服务端时间戳、源端时间戳、StatusCode 是协议内建的这是它相对前两者的突出优势代价是重地址空间浏览、会话管理、证书配置接入成本高1.4 归一四元组三类协议的差异再大进数据库前都应该归一到同一个形状时间戳 ts 设备/测点 deviceIdmetric 值 value 质量位 qualityMQTT 消息解析出测点和值时间戳取 payload 内嵌的有就用质量默认 good、断连时打 badModbus 轮询按点表把地址翻译成测点名时间戳取轮询时刻读失败补一条 bad 质量记录OPC UA 天然带全四元组直接映射归一不是丢信息是把差异挡在汇聚层之外——汇聚层之后的存储、计算、告警面对的都是同一种数据不必再关心它从哪条协议来。二、点表接入的元数据中心2.1 点表是什么点表tag list / point registry是一张清单每个测点一行记录它从哪来、怎么读、怎么换算。对 Modbus 来说是地址翻译表对 OPC UA 来说是节点映射表对 MQTT 来说是 payload 字段映射表。一张典型的点表// 点表接入配置的单一事实来源 pointReg table(1:0, deviceIdmetricprotocoladdressdataTypescaleoffsetdeadbandunit, [SYMBOL, SYMBOL, SYMBOL, SYMBOL, SYMBOL, DOUBLE, DOUBLE, DOUBLE, SYMBOL]) // 两行示例 // (PUMP-007, bearingTemp, modbus, 40011, int16, 0.01, 0.0, 0.5, °C) // —— 40011 号保持寄存器读到 4201乘 0.01 得 42.01°C变化小于 0.5 不上报 // (PUMP-007, vibRms, mqtt, factory/line3/pump007/vib, json, 1.0, 0.0, 0.0, mm/s) // —— MQTT 主题下的 JSON 字段原值即工程量2.2 点表驱动加测点是改配置不是改代码点表的核心价值是把接入做成配置驱动采集程序读点表干活——轮询哪些地址、解析哪些字段、怎么换算、死区多少。新增一个测点加一行点表、重载配置不需要改一行采集代码。没有点表的接入每个测点的地址、换算、上报逻辑都散落在代码和脚本里三个月后没人说得清 40011 是什么——这是很多老平台接入层变成黑盒的开始。2.3 点表落库配置即数据点表本身也存进数据库设备档案维度表旁边有三个好处配置变更有历史哪天把 scale 从 0.01 改成 0.001可追溯查询时可 join分析这个测点的单位/换算参数和查数据一样方便多网关共享点表是库里的表所有采集网关从同一处读配置不会各自维护一套三、汇聚层一个流表收口所有协议3.1 统一接入流表所有协议的数据最终都写进同一个流表——这就是汇聚点// 统一接入流表四元组 原始值 share streamTable(1:0, tsdeviceIdmetricrawValuequality, [DATETIME, SYMBOL, SYMBOL, DOUBLE, SYMBOL]) as ingestStream两个设计要点存原始值rawValue不存换算后的值——换算放入库后第五节展开质量位quality是一等公民不是可选字段3.2 质量位坏数据要带伤入库quality通常取三个值good可信、bad采集失败/设备报错、uncertain超时边缘/转换可疑。关键纪律是采集失败不要静默跳过要写一条 bad 记录。比如 Modbus 读 40011 超时写(10:00:05, PUMP-007, bearingTemp, NULL, bad)为什么带伤入库而不是不写因为下游要区分两种情况温度确实是 42°C 没变和根本没采到。如果失败就跳过时间轴上两者都是没有数据死区上报和采集故障就没法区分了第四节会看到这个区分多重要。OPC UA 天然带质量位直接映射MQTT 和 Modbus 的质量位由采集端补——读失败打 bad超过半个轮询周期没消息打 uncertain。3.3 协议插件的分工DolphinDB 提供 MQTT、OPC UA、Modbus 等协议插件配合采集网关职责分工是协议插架/网关只管把某个协议的数据搬进汇聚流表——翻译协议不做业务汇聚流表之后订阅做换算、清洗、落宽表——一切业务逻辑都在库内、都是 SQL这个分工让加一种新协议的改动面收敛到换一个插件/网关汇聚层之后的全部逻辑不动。反过来如果把换算、过滤写死在采集代码里每换协议都要把业务逻辑重写一遍。四、死区别把没变化的数据当价值4.1 周期上报 vs 变化上报温度这种慢变量一小时可能只漂 2°C。如果按 1 秒周期上报3600 条里 3590 条的信息量和前一条几乎相同——带宽、存储、写入压力全花在没变化上。死区deadband是变化上报的判断值相对上次上报的变化超过阈值才上报否则不上报。点表里那列deadband就是它按测点配置慢变量温度、压力给大死区如 0.5快变量振动死区给 0 或极小——振动本身就是波动的死区会把信号吃掉4.2 两种死区固定死区绝对值判断|value - lastSent| deadband才上报。简单直观适合量程稳定的测点百分比死区相对判断变化超过量程的百分比才上报。适合不同设备量程差异大的测点满量程 100 和满量程 1000 的传感器用同一个百分比死区判断在采集端执行它省的是传输和存储放在库里做就没意义了但配置在点表里——集中管理改死区不用动采集代码。4.3 死区的下游语义最后值保持死区开了之后时间轴变成稀疏的值没变的时段没有数据。下游消费这份数据语义必须是最后值保持last value carry-forward——10:00:03 报了 42.0110:00:04~10:05 没数据含义是这期间一直是 42.01而不是这期间未知。在 DolphinDB 里这就是前向填充的语义// 死区后的稀疏序列 → 按设备测点前向填充成连续序列 select deviceId, metric, ts, ffill(rawValue) as value from ingestTable context by deviceId, metric这里能看出质量位的价值如果 10:00:03 之后有一条bad记录采集故障前向填充该停在 bad 处——没变化所以没报和设备坏了没采到是两种完全不同的没有数据质量位就是用来区分它们的。五、工程量转换在哪儿做5.1 原始值 → 工程量Modbus 读回来的 4201 不是温度是待解释的数。解释规则通常很简单线性换算乘系数加偏移参数就在点表的scale和offset列里工程量 rawValue × scale offset 4201 × 0.01 0 42.01 °C5.2 为什么入库后做而不是采集端做这是接入层一个方向性的决策。三种做法里——采集端换算、网关换算、入库后换算——入库后做是工程上稳健的一种可重算换算参数错了厂商把系数发错是常事改点表的 scale重算一遍历史就修正了。采集端换算的话历史里存的已经是错的工程量原始信息丢了可审计库里同时有 rawValue 和换算参数任何人都能验证 42.01 是怎么来的采集端保持薄采集程序只搬数不解释换逻辑不用动采集链路代价是存储上多存一份原始值宽表落盘时通常只落换算后的工程量rawValue 在汇聚流表里过路即可。5.3 转换作为流式 SQL汇聚流表订阅里做换算一行 SQL 的事// 入库路径汇聚流表 → join 点表取换算参数 → 换算 质量过滤 → 落盘 converted select i.ts, i.deviceId, i.metric, i.rawValue * p.scale p.offset as value, i.quality from ingestTable as i inner join pointReg as p on i.deviceId p.deviceId and i.metric p.metric where i.quality good换算参数改动时这条路径自动跟着点表生效——改配置即改行为不用重新部署任何东西。六、断线与补采6.1 断档的三种处理姿势采集链路断网车间到机房的光纤被挖断是真实场景时有三种姿势不处理断档就是空白。下游若把空白当值没变会把故障静默成平稳——最差的一种打标记断档期间按测点写 bad 记录或断档检测任务事后补标记下游明确知道这段时间不可信网关缓存 回灌补采网关本地缓存断线期间的数据链路恢复后按原始时间戳回灌第一种不该选。后两种按场景组合短断分钟级打标记就够长断小时级且数据重要的测点上网关缓存。6.2 回灌的关键时间戳用采集时刻补采回灌容易犯的错数据恢复后一次性写入时间戳用了写入时刻——一段历史数据挤在恢复后的一秒钟里时序完全失真。正确做法是网关在采集时就打好时间戳缓存的就是带时间戳的记录回灌时原样写入汇聚流表断线 10:00 ~ 11:00网关缓存了 3600 条各带自己的采集时间戳 11:00 链路恢复回灌 → 库里 10:00~11:00 的数据按真实节拍落位回灌期间要注意流表消费端的吞吐余量一小时的数据几秒内涌入下游换算、落盘的订阅要扛得住瞬时高峰这也是接入层做容量规划时容易漏算的一项。6.3 补采与死区的配合死区在断线期间照常工作网关本地判断所以回灌的数据也是稀疏的变化序列不会因为缓存就膨胀成全量。补采数据和实时数据在汇聚流表里形状完全一致——下游不需要任何特殊处理这就是归一设计的红利。七、写在最后接入层是平台的第一公里也是最容易做糊的一公里。把全文收成几条协议差异归一到四元组时间戳 测点 值 质量。差异止步于汇聚层后面的一切不再关心协议点表是接入的中枢地址、换算、死区、单位都在点表里加测点是改配置不是改代码质量位是一等公民坏数据带伤入库别静默跳过——没变化和没采到必须可区分死区对付慢变量配置在点表、执行在采集端、下游按最后值保持理解换算入库后做可重算、可审计采集端保持薄补采用采集时刻的时间戳回灌和实时数据形状一致下游无感最后给一个总原则也是本文反复出现的那句话的完整版采集端只做四件事——取到原始值、打上采集时间戳、按死区决定是否上报、断线时本地缓存。其余一切换算、清洗、对齐、落盘都在库内用 SQL 做。采集端越薄协议越多越不乱库内越多错了越能改。第一公里少偷懒后面十八公里才跑得顺。