14.2 IoT DC3 端到端项目实战
14.2.1 项目背景与需求定义
多数失败的物联网项目,问题并非出在编码实现,而是出在开始写代码之前——需求定义阶段。团队花大量时间讨论“我们要做一个强大的物联网平台”,却没人定义“强大”的具体工程边界。功能清单列了几十项,优先级全是 P0,最后交付时核心链路跑不通,边缘功能却做得无比精致。这种“需求镀金”现象在物联网项目中尤其普遍,因为物理世界的接入维度多、约束链长,需求方和开发方都容易忽略工程边界的存在。
IoT DC3 是一个定位明确的开源工业物联网平台。它的设计目标是连接现场设备,覆盖设备管理、数据采集、规则引擎和数据服务等核心能力,而不是试图成为一个包罗万象的“万物互联操作系统”。这一务实定位,使它成为理解物联网平台工程边界的理想参照物。从典型的开源 IoT 平台架构看,核心层通常由设备管理、数据持久化、规则引擎和协议适配等几个职责清晰的模块组成,协议驱动独立部署,通过消息队列与主服务异步通信。这种解耦设计决定了需求定义阶段必须回答:你的场景中,协议驱动需要支撑多少种协议?设备上行数据的峰值吞吐量是多少?规则引擎的实时性要求到什么级别?
这意味着开源的工程边界,不一定是你项目实际需要面对的边界。在需求定义阶段,最关键的产出不是“能做多少”,而是“本轮不做什么”。这需要你在理解平台能力的基础上,对真实业务场景做一次穿透式梳理。
以下把 14.1.1 的方法论落到一个贯穿本章的例子上:基于 IoT DC3 搭建一个智能工厂管理平台。
某中等规模电子制造工厂,拥有约 2000 台设备,包括 SMT 贴片机、回流焊机、AOI(Automated Optical Inspection,自动光学检测仪)和温湿度传感器。当前的工程痛点:设备状态靠人工巡检,数据格式不统一——部分设备支持 Modbus TCP,部分只输出串口数据,还有几台老旧设备走的是自定义二进制协议。生产异常只能等操作工发现再上报,从故障发生到人工确认的平均耗时大约在四十分钟的量级。
与工厂运营团队多次沟通后,业务需求被收敛为四条核心目标:设备统一接入与状态实时采集;历史数据存储与趋势分析;告警规则配置与多通道推送(车间看板、微信、邮件);基于设备数据做预测性维护的初步尝试。这四条需求与工厂的运营痛点一一对应:设备接入解决数据孤岛,存储和分析解决“有数据但看不见”,告警解决响应滞后,预测维护解决被动维修。
针对这一例子,功能模块可以按以下方式划分。
设备接入模块:负责协议适配。智能工厂中涉及 Modbus TCP、串口(自定义协议)以及部分支持 MQTT 的新设备。不同协议对应不同驱动,驱动贴近现场设备运行,采集的数据经消息队列上报云端,不直连核心服务。这一层不做数据存储,只做格式转换和数据转发。
设备管理模块:负责设备的注册、分组、状态跟踪和生命周期管理。启停机、固件版本、在线状态、归属产线等元数据在此维护。
数据中心:负责采集数据的接收、持久化和查询。时序数据库存储设备位号值,关系库或文档库存储设备配置和事件记录。告警引擎与数据中心联动,当数值超过设定阈值时触发告警。
智能分析模块:负责模型训练、推理和规则联动。这一轮做轻量化上线——先用基于统计的方法做异常检测(如离群点识别、趋势偏移),不急于上线深度学习模型。这一模块的具体工程实现将在后续小节展开,它也是后续集成 AI 能力的切入点。
应用与服务层:这一层面向人和业务系统提供能力。现场运维人员通过设备列表、数据看板和告警页面理解设备状态;生产管理系统通过接口读取设备事件、工单和统计结果;MES(Manufacturing Execution System,制造执行系统)与 ERP(Enterprise Resource Planning,企业资源计划)等系统则通过 API(Application Programming Interface,应用程序接口)完成跨系统协同。
功能模块划分完成后,还需要做一件容易被忽视的事:定边界。在这个例子中,以下能力被明确划入第二期或第三期:设备 OTA(Over-the-Air,空中下载)升级、设备影子(Device Shadow)、多租户隔离(当前只有单一工厂)、以及基于强化学习的全自动排产方案。边界定义的意义在于,它让开发团队和业务方都知道这只是一个起点,而不是终点。团队可以聚焦在四条需求上迭代,而不需要为“万能平台”这个虚目标分散精力。每次需求评审时,只要问一句“这个功能是否直接服务于四条核心需求”,大部分镀金需求自己就消失了。
需求定义阶段的交付物,是一份可评审、可争议、可修改的需求文档,辅以明确的功能模块清单和边界说明(包含明确的“不做”清单)。这份文档不追求完美,但必须有优先级、有取舍。需求边界一旦清晰,后续的架构设计、测试与验收就有稳定的判定依据;边界含糊,这些环节都会陷入反复返工。
14.2.2 系统架构设计
IoT DC3 可以按四层理解:南向设备层、协议 Driver 层、平台服务层和应用展现层。这个分层的价值不是画图,而是明确哪些调用可以同步、哪些数据必须异步,以及服务寻址和配置由谁负责。
四层职责
- 南向设备层:传感器、PLC、控制器和第三方系统,使用 MQTT、Modbus、OPC UA、IEC 104 等协议。
- 协议 Driver 层:每种协议独立部署,负责连接、编解码、位号读写和状态上报。Driver 可按现场需要下沉到边缘节点。
- 平台服务层:Auth 负责认证授权;Manager 负责 Driver、设备、模板、位号和属性等元数据;Data 负责位号值、命令、回执、告警数据与查询;Agentic 负责模型、会话和 Spring AI Tools。
- 应用展现层:Web、第三方应用和 API 客户端,经 Gateway 统一访问平台。
当前服务治理与消息基础设施
IoT DC3 当前没有 Nacos 或其他独立服务注册中心。Gateway 路由和 gRPC Channel 使用固定服务名,Compose 网络通过 DNS 解析,并允许用 CENTER_*_HOST、GATEWAY_ROUTE_*_URI 等环境变量覆盖地址。默认配置保存在项目 YAML 中,部署参数通过环境变量注入。
内部消息通过统一消息端口交换,RabbitMQ 是默认适配器;代码还提供 Kafka、RocketMQ、Pulsar、ActiveMQ 与 MQTT 5 适配器,由 DC3_MQ_TYPE 选择。Data 把位号命令与自定义命令交给消息端口,Driver 消费后执行协议操作,并返回结果回执、位号值、状态和事件。dc3-driver-kafka 是南向数据源驱动,与内部 Kafka 适配器不是一回事。
该架构的工程取舍是:管理与元数据查询需要即时结果,因此使用 REST/gRPC;设备命令和上行数据需要异步解耦与削峰,因此通过统一消息端口交换,默认适配器为 RabbitMQ。边界清晰比组件数量更重要。
14.2.3 核心模块实现
理解 IoT DC3 的实现,应沿三条真实链路阅读源码,而不是套用“注册中心 + Kafka + 独立命令服务”的通用模板。
Driver 业务注册与元数据同步
Driver 启动后,DriverRegisterService 通过 gRPC 调用 Manager 的 driverRegister。注册内容是 Driver 的业务身份、配置和元数据,不是向 Nacos 等注册中心登记 IP。设备、位号、模板和属性等运行时元数据也通过 Manager Facade 查询,并缓存在 Driver 进程内的 Caffeine 中。
位号值上报与数据处理
协议实现通过 DriverProtocol 完成真实设备读写。读取或订阅得到的数据转换为统一 PointValue 后,由 DriverSenderService 交给消息端口;默认 RabbitMQ 适配器负责具体发布。Data 的 PointValueReceiver 从同一端口接收消息:低于批处理阈值时直接保存,高于阈值时进入 PointValueIngestBuffer 进程内缓冲后批量写入。Data 同时维护最新值的本地 Caffeine 缓存,并通过 TsdbStore 写入历史数据;默认实现为 TimescaleDB。持久化完成后再触发告警规则处理。
位号命令与结果回执
位号读写入口位于 Data。Data 按 Driver 服务名把命令交给消息端口,Driver 的 PointCommandReceiver 检查 expireAt 和 commandId,使用设备级锁串行化同一设备的协议操作,然后调用 DriverReadService 或 DriverWriteService。成功或失败结果仍经消息端口回传 Data。ack、reject、nack/requeue、TTL 与死信交换机是默认 RabbitMQ 适配器的具体语义;换用其他适配器时,必须验证等价的确认、重试、过期与失败隔离行为。
工程边界
- 没有独立 Command Service,命令入口和回执处理属于 Data。
- 默认数据面使用 RabbitMQ;替换 Broker 后,命令、回执、位号值、状态和事件仍走同一消息端口,但确认、顺序、死信与延迟能力要按适配器重新验证。
- 没有 Redis 两级设备影子;Driver 缓存元数据,Data 用本地 Caffeine 缓存最新位号值。
- 没有统一
DeviceDriver或全局ConnectionManager,协议 Driver 按能力接口与各自连接模型实现。
沿这三条链路阅读代码,可以把“同步管理调用”和“异步设备数据流”准确分开,也能直接定位性能与可靠性责任边界。
14.2.4 设备接入与数据流
设备接入的核心挑战不是网络连通性,而是协议语义收敛。MQTT、Modbus、OPC UA 的连接模型、时序方式、数据表达各不相同——MQTT 依赖设备主动发布,Modbus 由 Driver 轮询,OPC UA 可以订阅节点变化。Driver 层需要将这些异构协议收敛为统一的 PointValue 和命令模型。协议入口不同,进入平台后的数据链路才一致。
从设备报文到位号值
以 MQTT 场景为例,设备报文可以使用 JSON,但 Topic 和字段结构由具体 Driver 定义,不存在全平台唯一的固定 Payload。Driver 完成连接、订阅、反序列化和设备/位号映射,再调用统一发送服务。
下面是一个简化的设备属性上报 JSON 结构示例,仅用于说明字段设计思路,并非 IoT DC3 所有 MQTT Driver 的强制格式:
{
"deviceCode": "device-001",
"timestamp": 1700000000123,
"values": {
"temperature": 25.6,
"humidity": 68.2,
"pressure": 1013.2
},
"qos": 1,
"msgId": "a1b2c3d4"
}deviceCode对应平台已注册的设备身份,Driver 在启动时通过 Manager 元数据同步获得该映射。values内的键是位号标识符,值可以是数值、字符串或布尔,Driver 根据模板定义判断类型。msgId用于上行去重,Data 消费侧会根据 msgId(或组合 deviceCode + timestamp)做幂等判断。
实际项目中,如果位号数量超过数百,JSON 解析和序列化的 CPU 开销会变得显著。此时可考虑换用 Protobuf 或 MessagePack——Payload 结构不变,只是序列化/反序列化由 Driver 层替换,Data 侧保持统一消费接口。
数据流各阶段组件与功能说明
表 14-2 展示了上行位号值从设备到消息端口、缓存与时序存储端口的职责和典型风险。
表14-2 上行位号值数据流各阶段的职责与风险
| 阶段 | 组件 | 主要职责 | 并发/一致性约束 | 关键风险 |
|---|---|---|---|---|
| 协议接入 | 设备侧协议(MQTT/Modbus/OPC UA) | 按照协议规范发送或响应数据 | 设备连接保持、心跳保活 | 网络闪断导致数据丢失;重连后 Topic/节点重复订阅 |
| 协议解析 | Driver(DriverProtocol 实现) | 反序列化原始报文,按 Manager 元数据转换为 PointValue 对象 | 连接与并发模型由具体协议实现决定;Driver 本地 Caffeine 缓存元数据 | 报文格式变化、阻塞调用或连接状态处理不当导致解析和资源问题 |
| 消息投递 | DriverSenderService → 消息端口 | 发布统一 PointValue;默认 RabbitMQ 映射到相应 Exchange | 路由、确认、顺序、持久化与批量能力由所选适配器决定 | 生产速率超过消费速率;Broker 容量或保留策略失配 |
| 异步消费 | Data 的 PointValueReceiver | 从消息端口接收,按阈值直接保存或进入 PointValueIngestBuffer | 确认与重投语义须和适配器契约一致;缓冲阈值需实测 | 慢消费导致积压;重投产生重复;批量失败扩大影响 |
| 缓存更新 | Data → Caffeine 本地缓存 | 维护本实例可见的最新位号值,供查询快速返回 | JVM 进程本地状态,多实例间不能假定强一致 | 缓存陈旧、实例间差异、JVM 内存压力 |
| 持久化 | Data → TsdbStore | 写入历史位号值;默认适配器为 TimescaleDB | 批量、保留、聚合与查询能力取决于所选时序库适配器 | 写入或查询瓶颈;保留策略、索引或分区配置失配 |
| 告警触发 | Data → 告警规则处理 | 在持久化完成后检查规则并生成告警 | 需要定义重复数据、重试和告警幂等语义 | 规则错误导致误告或漏告;重放触发告警风暴 |
下行命令的异步回执
下行命令走反向异步链路:客户端经 Gateway 调用 Data 的位号命令接口,Data 将命令体交给消息端口;目标 Driver 消费后执行设备操作,结果回执再经同一端口返回 Data。默认 RabbitMQ 适配器映射到 dc3.e.point_command 等 Exchange。客户端应通过 WebSocket 订阅或轮询 Data 提供的命令状态 API,而不是假定 HTTP 请求会一直阻塞到设备返回。
Driver 在执行前使用 commandId 做去重与过期检查,并以设备级锁串行化同一设备的协议操作。commandId 的生成方、去重状态保留时间以及跨实例是否共享,应以当前接口与实现为准并通过重放测试验证;本地锁和进程内去重都不能自动提供跨 Driver 实例的全局互斥。
容量观测与瓶颈判断
容量设计的原则是:先观测,后优化。默认栈在 RabbitMQ 控制台观察消息速率、积压与未确认消息,在 Data 监控端点观察消费与写入延迟,在 TimescaleDB/PostgreSQL 侧观察 hypertable、查询与磁盘 IO;换用其他适配器时改用对应指标。只有压测证明单一链路成为瓶颈后,才考虑分区、冷热分层或更换适配器。不能因为仓库“支持”某个 Broker 或时序库,就宣称目标负载已经得到验证。
工程检查清单:
- [ ] 设备连接稳定性:使用 MQTT 遗嘱消息和自动重连策略,Modbus Driver 配置超时重试。
- [ ] 上行消息幂等:Data 侧按
msgId或deviceCode + timestamp去重,避免重复写入。 - [ ] 下行命令防重:客户端生成全局 UUID 作为
commandId;Driver 侧设备锁超时设置(例如 30 秒)。 - [ ] 积压告警阈值(示例):RabbitMQ 队列深度超过 10,000 且持续 60 秒时告警,实际阈值应按基线和SLA校准。
- [ ] 数据库写入慢查询(示例参数):监控
dc3_point_value表的track_io_timing,设置 PostgreSQLlog_min_duration_statement = 200ms,实际参数应按现场负载校准。
14.2.5 AI 运维能力构建(非开箱即用)
IoT DC3 的 987c96d50 源码快照中,Agentic Center 已实现模型配置、会话管理、Spring AI @Tool 调用和 Web/HTTP 对话;Gateway 的 /mcp 端点按 2025-06-18 修订版处理 initialize、notifications/initialized、ping、tools/list、tools/call,当前仅声明 Tools 能力,未实现 Resources、Prompts 或 Tasks。项目 Compose 没有 TensorFlow Serving、训练任务、模型卷,也没有 Agentic 订阅 Data 实时位号流的默认链路。因此本节只把预测性维护作为可选工程扩展讨论,不能写成当前开箱即用能力。
先规则,再统计,最后模型
异常检测可以分三档:固定阈值处理明确红线;滑动窗口、IQR、Z-score 等统计方法处理缓慢漂移;有监督或无监督模型处理多变量耦合、时间依赖和难以手写规则的模式。三档不是替代关系。模型只有在基线规则无法满足且数据质量、标签和收益足以支撑时才值得引入。
一个可选的预测性维护扩展
若项目确需模型推理,可以按以下边界设计:
- 从 Data 的历史查询接口取得经租户授权的位号数据。
- 在平台外完成时间对齐、缺失值处理、窗口化和训练。
- 把模型部署为独立、受认证保护的推理服务。
- 由授权任务读取 Data 数据并调用推理服务。
- 将推理结果写回一个明确的衍生位号,例如
bearing_anomaly_score。 - 复用现有规则与通知链路判断阈值和持续时间。
模型类型、窗口长度和阈值必须由数据验证。LSTM、窗口 32、阈值 0.85 都只能作为假设示例,不能写成 IoT DC3 默认配置。Spring AI Tools 适合编排查询、解释和受控执行,不等于承担高频流式推理;MCP 也只负责把授权的 Tools 暴露给外部 Agent,不负责训练和部署模型。
安全边界至少包含输入值域校验、推理端点认证与限流、模型版本审计、租户隔离和衍生位号权限。AI 能力不应绕过现有平台治理逻辑。
14.2.6 部署与测试
部署阶段要验证 IoT DC3 当前实际组件能否在容器网络中完整启动,并跑通 Driver 业务注册、位号值上报和位号命令回执。以 2026-08-29 的 987c96d50 快照为准,开发默认栈以 PostgreSQL/TimescaleDB 与 RabbitMQ 为基础,平台服务包括 Gateway、Auth、Manager、Data、Agentic,协议 Driver 按栈启用。可选栈提供其他 Broker、TSDB 与可观测组件;模板没有 Nacos,也没有模型推理容器或模型卷。
当前 Compose 拓扑
x-app-runtime-env: &app-runtime-env
DC3_MQ_TYPE: rabbitmq
DC3_TSDB_TYPE: timescale
POSTGRES_HOST: dc3-postgres
RABBITMQ_HOST: dc3-rabbitmq
CENTER_AUTH_HOST: dc3-center-auth
CENTER_MANAGER_HOST: dc3-center-manager
CENTER_DATA_HOST: dc3-center-data
CENTER_AGENTIC_HOST: dc3-center-agentic
services:
postgres:
container_name: dc3-postgres
rabbitmq:
container_name: dc3-rabbitmq
gateway:
environment: { <<: *app-runtime-env }
auth:
environment: { <<: *app-runtime-env }
manager:
environment: { <<: *app-runtime-env }
data:
environment: { <<: *app-runtime-env }
agentic:
environment: { <<: *app-runtime-env }
mqtt:
environment: { <<: *app-runtime-env }启动时使用 podman compose。depends_on 只能表达依赖关系,仍需结合 healthcheck 和应用重试等待 PostgreSQL、RabbitMQ 真正就绪。容器之间使用 dc3-postgres、dc3-rabbitmq、dc3-center-* 等服务名,不能把 localhost 当作其他容器。敏感变量应由 .env 或密钥管理注入,不提交真实凭据。
从零到第一条位号:版本化验收序列
服务起来不算部署成功,整条链路跑通才算。下面的序列对应 987c96d50 快照,并选择内置 Virtual Driver,避免额外依赖 MQTT Broker、Topic 与厂商 payload。生成的 ID 和 Token 必须用前一步真实返回值替换;若仓库 commit 不同,应先阅读该版本的 README 与官方 “First Device: End to End”,不能混用本节命令。这里给的是可核对的验收顺序,不承诺对未来版本逐字复制仍有效。
第 1 步:取代码。
git clone https://github.com/pnoker/iot-dc3.git && cd iot-dc3预期:得到含 dc3/、dc3-center/、dc3-driver/、Makefile 与 .env.example 的完整仓库。
第 2 步:起基础设施。
make up-db # Make 目标默认 podman compose;国内镜像源可用 make up-db-cn预期:PostgreSQL 与 RabbitMQ 容器运行;首次启动会按 extensions、common、auth、data、manager、history、agentic 的顺序初始化数据库。
第 3 步:验证服务健康。
podman ps
podman exec dc3-postgres psql -U dc3 -d dc3 -c '\dt dc3_auth.*'预期:dc3-postgres、dc3-rabbitmq 状态 Up;能列出 auth schema 的表。宿主机映射端口以 .env 为准(当前 Quick Start 为 PostgreSQL 35432、RabbitMQ AMQP 35672,容器内仍是 5432/5672)。
第 4 步:起平台服务并换取 Token。
source dc3/env/dev.env.sh
make up-dev # 等价 make up STACK=dev;启动顺序 Auth 先行、Gateway 最后
curl -s -X POST http://localhost:8000/api/v3/auth/token/salt \
-H 'Content-Type: application/json' -d '{"tenant":"default","name":"dc3"}'预期:返回 5 分钟有效的 salt;再用 /api/v3/auth/token/generate(携带 salt 与按规则哈希后的密码,哈希规则以官方 Quick Start 为准)换取 12 小时有效的 Token。此后所有请求统一携带 X-Auth-Tenant、X-Auth-Login、X-Auth-Token 三个头。Gateway 是唯一外部 HTTP 入口(8000 端口),Auth/Manager/Data 的直连端口只用于调试。
第 5 步:确认 Driver 注册并准备设备元数据。
curl -s -X POST http://localhost:8000/api/v3/manager/driver/list \
-H "$H_TENANT" -H "$H_LOGIN" -H "$H_TOKEN" -H 'Content-Type: application/json' -d '{}'预期:返回随栈启动的 Driver 列表——Driver 能出现在这里,说明 14.2.3 所述的 gRPC 业务注册已经成功。随后按同版本官方 Quick Start 创建 profile、位号(例如 Temperature,FLOAT、READ_WRITE)和绑定 Virtual Driver 的设备,记录 deviceId 与 pointId。要改用 MQTT 或其他协议时,应先确认对应 Driver、南向服务和属性模型已启用,再替换本序列中的 Driver 专属步骤。
第 6 步:配置 Virtual Driver 的位号属性并等待自动上报。
从 Virtual Driver 注册的 Point Attribute 列表中取得真实 attributeId,再通过 /api/v3/manager/point_attribute_config/add 为前一步的 deviceId、pointId 写入 configValue。这个配置完成后,Virtual Driver 会产生位号值,无需伪造一套并不存在的通用 MQTT Topic 或 payload。具体请求体以同版本官方 First Device 页面为准;attributeId 是运行时注册结果,不能写死在书中。
第 7 步:REST 查询位号值。
curl -s -X POST http://localhost:8000/api/v3/data/point_value/latest \
-H "$H_TENANT" -H "$H_LOGIN" -H "$H_TOKEN" -H 'Content-Type: application/json' \
-d '{"deviceId":"<DEVICE_ID>","pointId":"<POINT_ID>","page":{"current":1,"size":10}}'预期:返回该位号的最新记录(rawValue、calValue、numValue、createTime 等字段)——说明“Driver → 消息端口 → Data → 时序存储端口”上行链路贯通;默认适配器对应 RabbitMQ 与 TimescaleDB。
第 8 步:下发一条写命令。
curl -s -X POST http://localhost:8000/api/v3/data/point_command/write \
-H "$H_TENANT" -H "$H_LOGIN" -H "$H_TOKEN" -H 'Content-Type: application/json' \
-d '{"deviceId":"<DEVICE_ID>","pointId":"<POINT_ID>","value":"26.5"}'预期:接口立即返回 commandId,命令异步执行;仅 READ_WRITE/WRITE_ONLY 的位号可写,命令默认约 10 秒过期(expireAt),过期未执行即失败。
第 9 步:查命令回执。
curl -s "http://localhost:8000/api/v3/data/point_command_history/get_by_command_id?commandId=<COMMAND_ID>" \
-H "$H_TENANT" -H "$H_LOGIN" -H "$H_TOKEN"预期:能看到命令状态与回执;若状态为过期或失败,拿着 commandId 结合回执信息定位(常见原因见 14.3.5)。
第 10 步:看日志收尾。
podman logs dc3-center-data --tail 50
podman logs dc3-driver-virtual --tail 50 # 以当前 Compose 的实际服务名为准预期:Data 日志出现位号值消费与保存记录,Driver 日志出现注册与读写执行记录;默认栈再对照 RabbitMQ 管理台检查相关队列的积压和死信。换用其他 DC3_MQ_TYPE 时查看该适配器的等价指标。至此,上行与下行两条链路都有可检查的证据。
冒烟与性能测试
表14-3 冒烟测试场景与预期结果
| 场景 | 验证动作 | 预期结果 |
|---|---|---|
| 服务启动 | podman compose ps 与 readiness | 基础设施和所需服务健康 |
| Driver 注册 | 启动一个协议 Driver | Manager 收到 gRPC 业务注册 |
| 数据上报 | 按第 6 步配置 Virtual Driver 位号属性并等待上报;换协议时使用该 Driver 的同版本官方接入方式 | 位号值经消息端口进入 Data,并通过 TsdbStore 写入所选时序库;默认对应 RabbitMQ 与 TimescaleDB |
| 命令下发 | 调用 Data 位号命令接口 | 消息端口投递到目标 Driver,结果回执返回 Data;默认 RabbitMQ 语义可观测 |
| 故障恢复 | 暂停所选 Broker 或消费者后恢复 | 该适配器声明的重投、失败隔离、积压与告警行为符合配置 |
性能测试应分别观察 Driver 采集与锁等待、所选消息适配器的积压和确认状态、Data 消费与批量保存、所选 TsdbStore 的写入与查询延迟。默认栈对应 RabbitMQ 与 TimescaleDB/PostgreSQL;换用其他适配器必须采集其自身指标,不能拿未运行的调优报告替代实测。
14.2.7 可复现实验、验收指标与证据包
部署成功截图只能证明某一时刻服务启动过,不能证明系统在固定负载、故障和安全约束下可重复工作。出版级实战必须让第三方知道运行了什么版本、使用什么数据、怎样施加负载、指标如何计算,以及原始结果在哪里。没有实测的项目可以写设计和方法,但不能用数值冒充结果。
先冻结环境 manifest
每轮实验保存一份不可变 manifest,至少记录:
- IoT DC3 Git commit/tag、未提交补丁和仓库状态;
- 容器镜像 digest、Compose 文件及环境变量模板版本;
- OS、CPU、内存、磁盘、网络、Podman、JDK、Python;
DC3_TSDB_TYPE、DC3_MQ_TYPE、对应服务版本、Driver 和设备/模拟器固件版本;- 模型 Provider、模型 ID、服务版本、Prompt 哈希和 Tool schema 版本;
- RAG 语料、切分、Embedding、reranker 和索引版本;
- 测试数据名称、许可、切分和 SHA-256;
- seed、时区、NTP/时钟条件和运行时间。
密钥和个人数据不得进入 manifest;使用环境变量名、凭据 ID 或脱敏摘要。外部 Provider 无法保证确定性时,记录区域、请求参数和重复次数,不声称 seed 可以完全复现输出。
工作负载必须可重放
“模拟大量设备”无法复现。应固定设备数、每设备位号数、上报频率、payload 大小、读写比例、命令比例、持续时间和预热时间。故障实验还要固定网络延迟/丢包、断网窗口、消费者暂停、Broker/数据库重启时刻、并发 Agent 会话数,以及模型和 Tool 的超时/错误注入比例。
基线也要明确。例如:纯规则、无 AI;Agent 无 RAG;Copilot 只读;受约束 Agent。一次比较只改变主要变量;若硬件、数据或模型同时变化,就不能把差异全部归因于某一组件。
指标字典:先定义分母,再报告数字
表14-4 指标字典与聚合建议
| 层面 | 指标 | 分母/窗口 | 建议聚合 |
|---|---|---|---|
| 设备接入 | 注册成功率、稳定在线率、重连时间 | 目标设备/测试窗口 | 比例、P50/P95 |
| 数据链路 | 接收率、重复率、乱序率、端到端时延 | 应上报消息/已接收消息 | 比例、P50/P95/P99 |
| 命令链路 | 成功率、确认时延、过期率、重复执行率 | 已提交命令 | 比例、P50/P95 |
| 存储 | 写入吞吐、写入/查询时延、增长量 | 固定工作负载和窗口 | rate、P95、字节 |
| 可靠性 | 积压恢复、死信、RTO、RPO、数据缺口 | 每个故障场景 | 时长、计数 |
| RAG | Recall@k、忠实性、拒答准确率 | 版本化评测集 | 比例与置信区间 |
| Agent | 任务成功、参数正确、越权、接管、重复副作用 | golden tasks/攻击集 | 比例、零容忍项 |
| 成本 | 每万条遥测、每任务、每成功任务成本 | 明确计费与资源边界 | 币种、token、CPU时 |
时延起止点必须固定。例如端到端遥测时延可定义为模拟器生成时间到 Data 持久化确认;命令确认时延可定义为 API 接受 Action 到 Driver 回执。不同章节和图表必须使用同一定义。
重复运行和不确定性
每个场景应多次独立运行,报告样本数、中位数或均值、标准差或置信区间,并为长尾报告 P95/P99。预热数据与正式样本分开。LLM 实验需要保存逐任务结果和 trace,避免一次成功回答代表整体能力。
若样本量不足,应明确限制;若指标尚未运行,填 NA(未执行),而不是 0。0 表示测量后没有发生,NA 表示没有证据,两者含义完全不同。
故障和安全用例
最小实验包至少覆盖:
- 重复遥测和乱序时间戳;
- Driver 或网络短暂断开后重连;
- 所选消息适配器的消费者暂停与积压恢复;
- 数据库不可用和恢复;
- 用户权限不足与跨租户请求;
- 模型超时、Tool 超时和脏返回;
- Action 已执行但回执丢失;
- 相同
idempotency_key重放; - 人工接管和 kill switch。
每个用例记录期望状态、实际状态、副作用、日志和恢复结果。设备控制实验应优先使用模拟器、shadow mode 或非安全关键设备,不能为了演示绕过 PLC/SIS 联锁。
出版证据包
建议为每次书稿引用的实验保存:
experiments/EXP-14-E2E-01/
├── README.md # 复现步骤与已知限制
├── manifest.json # 版本、环境和数据哈希
├── workload.yaml # 负载与故障参数
├── commands.txt # 实际执行命令
├── raw/ # 原始指标、日志与逐任务 trace
├── summary.json # 指标定义和汇总
├── failures/ # 失败样本与复盘
└── figures/ # 从 raw 生成图表的方法正文中的实测数字必须反链实验 ID 和原始结果位置。无法公开的数据应提供脱敏样本或可替代生成器,并说明它与真实数据的差异。实验脚本、数据和第三方组件还要标明许可。
实验卡 EXP-14-E2E-01
- 假设:在固定设备负载和故障窗口下,系统满足事先定义的数据、命令、安全与恢复门槛;
- 固定项:commit、镜像 digest、硬件、依赖、数据 hash、seed、模型/Prompt/Tool/RAG 版本;
- 基线:无 AI、只读 Copilot、受约束 Agent;
- 指标:本节指标字典中实际执行的项目;
- 阈值:由场景 SLO 和风险分析确定,高风险无审批执行、跨租户越权、重复设备副作用为零;
- 结果:当前书稿未附真实实验包时全部标记 NA,不预填宣传性数值。
可复现并不意味着不同环境得到完全相同的微秒级结果,而是第三方能够重建主要条件、复算指标、解释差异,并判断结论是否在声明的边界内成立。