3. 工业设备软件追踪数据模型
在工业设备软件中,追踪数据模型是诊断平台的数据骨架。它决定了我们能否把一次设备动作、一次控制指令、一次异常停机,还原成一条可读、可查、可关联的调用链。与互联网微服务不同,工业设备软件的追踪对象往往同时包含软件进程、PLC 扫描周期、现场总线通信、边缘网关和云端服务,因此数据模型必须从一开始就考虑跨进程与跨设备的语义一致性。
Trace 与 Span:追踪的基本单元
Trace 表示一次完整的业务或控制事务,例如“启动 3 号注塑机合模动作”或“下发一次温度设定值”。它由一个全局唯一的 traceId 标识,并包含若干 Span。Span 是 Trace 中的一段有名字、有起止时间、有状态的操作,例如“接收 MQTT 指令”“调用运动控制库”“等待 PLC 应答”“写入本地日志”。每个 Span 至少应包含:
- spanId:Span 自身唯一标识;
- parentSpanId:父 Span 标识,根 Span 为空;
- traceId:所属 Trace 标识;
- name:操作名称,建议采用“设备.模块.动作”命名,如
injection.motion.close_mold; - startTime / endTime:起止时间戳,建议统一使用 UTC 毫秒或微秒;
- status:OK、ERROR、TIMEOUT、CANCELLED;
- attributes:键值对,如 deviceId、stationId、plcAddress、setpoint、actualValue;
- events:Span 内的时间点事件,如“收到 ACK”“触发急停”。
在工业场景中,Span 的时间语义需要特别约定。设备控制服务通常同时存在“墙钟时间”和“设备周期时间”。建议以墙钟时间作为统一时间轴,同时用 attribute 记录 PLC 扫描周期号或设备本地 tick,便于后续对齐。若设备时钟与服务器时钟存在偏差,应在采集端记录 clockOffset,而不是在查询时猜测。
上下文传播:跨进程与跨设备的关键
上下文传播解决的是“父 Span 如何把 traceId、spanId 和采样标记传给子 Span”。在单进程内,通常通过线程本地变量或异步上下文传递;跨进程时,需要把上下文注入到消息头、HTTP Header 或工业协议的自定义字段中。常见做法是采用 W3C Trace Context 的 traceparent 格式:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
版本- traceId - parentSpanId - 采样标志
在工业设备软件中,跨设备传播往往不能依赖 HTTP。例如设备控制服务通过 OPC UA 写入 PLC,再由 PLC 触发边缘网关上报,此时 traceparent 无法直接写入 PLC 寄存器。可行的做法是:在控制服务侧生成 traceId,并将其写入设备侧可观测的“工单号”或“批次号”字段;边缘网关收到该字段后,重新以该 traceId 创建根 Span,并标注 trace.origin=device-control。这样虽然链路中间存在“上下文断点”,但通过业务字段仍可完成关联。
工业设备软件中的调用链特点
工业设备软件的调用链与互联网服务有显著差异。第一,实时性约束强:运动控制、PLC 扫描、安全回路往往要求在毫秒级完成,追踪埋点不能引入明显抖动。因此 Span 的创建和上报应尽量异步,或采用环形缓冲区批量上报。第二,调用链深度浅但宽度大:一台设备可能同时有几十个轴、上百个 IO 点,Trace 更像“扇出”结构,而不是长链。第三,存在周期性重复:同一动作每个生产节拍都会执行,需要区分“单次 Trace”和“周期聚合”。第四,设备侧资源受限:PLC 和嵌入式控制器无法运行完整 SDK,只能通过轻量字段或日志间接参与追踪。
追踪数据与日志的关联
追踪和日志不是两套独立数据。推荐在日志中显式写入 traceId 和 spanId,例如:
2025-03-18T10:22:31.482Z INFO [traceId=4bf92f35...4736 spanId=00f067aa0ba902b7]
device=injection-03 event="mold closed" actualPos=120.4mm setpoint=120.0mm
这样在诊断平台中,既可以按 traceId 拉出完整调用链,也可以从某条错误日志反查其所属 Span 和 Trace。对于无法写入 traceId 的 PLC 日志,可通过时间窗口 + 设备 ID + 工单号进行近似关联,并在结果中标注关联置信度。
实战任务:为设备控制服务设计追踪模型
假设我们有一个设备控制服务,负责接收 MQTT 指令、调用运动控制库、通过 OPC UA 写入 PLC,并等待 PLC 应答。下面给出一份可直接参考的追踪模型设计表,字段和取值均为教学示例,实际项目应按设备协议和平台规范调整。
| Span 名称 | 父 Span | 关键 attributes | status 判断 | 关联日志字段 |
|---|---|---|---|---|
| injection.cycle.start | — | deviceId, stationId, cycleNo | OK / ERROR | traceId, spanId, cycleNo |
| mqtt.receive.command | injection.cycle.start | topic, qos, payloadSize | OK / TIMEOUT | traceId, spanId, topic |
| motion.close_mold | injection.cycle.start | axisId, setpoint, actualValue, durationMs | OK / ERROR | traceId, spanId, axisId |
| plc.write.setpoint | motion.close_mold | plcAddress, value, protocol | OK / ERROR | traceId, spanId, plcAddress |
| plc.wait.ack | motion.close_mold | timeoutMs, retryCount, ackCode | OK / TIMEOUT | traceId, spanId, ackCode |
实现时,控制服务在收到 MQTT 指令后创建根 Span,并把 traceId 写入日志 MDC 和 OPC UA 工单号字段。运动控制库和 PLC 通信模块分别创建子 Span,记录 setpoint、actualValue、ackCode 等关键参数。边缘网关从工单号中提取 traceId,重建根 Span 并继续向下传播。最终在诊断平台中,按 traceId 查询即可看到从指令接收到 PLC 应答的完整链路,同时可下钻到每条日志和每个参数变化。