边缘网关架构设计总览

工程铁律: 任何性能优化不得以牺牲稳定性为代价;任何架构优化不得增加系统恢复复杂度。

本文档是 edgeCore 运行时架构权威说明,以 影子设备(ShadowCore) 为数据枢纽,梳理南向采集 → 影子 → 边缘计算 / 持久化 / UI → 北向上报的完整热路径。

相关文档 说明
影子设备设计 Shadow 模型与 COW / Ingress
影子设备系统联动关系 组件关联细节
南向驱动矩阵 协议能力与覆盖率
产品说明 / PRODUCT 能力与价值
用户手册 部署与操作
ScanEngine 重构方案 调度内核规范

1. 系统总览

1.1 设计原则

  1. 配置唯一源data/config.db(bbolt);运行时历史与缓存在 data/runtime.db
  2. 运行时真源:纯内存 ShadowCore;REST / WebSocket 只读影子(禁止 live driver fallback)。
  3. 调度驱动采集ScanEngine 统一时间、资源、执行与任务状态;Driver 为纯执行(禁止内部 ticker / 自建重连循环)。
  4. 统一扇出ShadowBridge → DataPipeline 承载边缘规则、北向推送、历史落库。
  5. 统计 SLA 可观测GET /api/diagnostics/scan-engine + sla_warnings + UI 通道监控。
  6. 长操作不阻塞管理面:设备发现 / OPC UA Browse 走异步 Job;北向地址空间默认原地 Sync;共享链路 I/O 与重连统一 channelMu

edgeCore 架构 · ScanEngine 统一调度

edgeCore 架构 · ScanEngine 统一调度:13 种南向驱动经 ScanEngine 写入影子设备实时快照,再联通虚拟设备、边缘计算与北向接口。

1.2 分层架构(Mermaid)

flowchart TB
  subgraph UI["管理面"]
    WEB["Web UI (Vue 3)"]
    REST["REST API"]
    WS["WebSocket /api/ws/values"]
  end

  subgraph ORCH["编排层"]
    CM["ChannelManager"]
    SE["ScanEngine + ExecutionLayer"]
    ECM["EdgeComputeManager"]
    NBM["NorthboundManager"]
    DSM["DeviceStorageManager"]
  end

  subgraph SOT["运行时真源"]
    SI["ShadowIngress"]
    SC["ShadowCore (COW)"]
    VSE["VirtualShadowEngine"]
  end

  subgraph SB["南向"]
    DRV["Drivers ×13<br/>Modbus / OPC UA / S7 / ENIP / …"]
    CONN["ConnectionManager"]
  end

  subgraph NB["北向"]
    MQTT["MQTT / Sparkplug B"]
    OPCS["OPC UA Server"]
    BACnet["BACnet Server"]
    HTTP["HTTP / EdgeOS"]
  end

  subgraph STORE["持久化"]
    CDB["config.db"]
    RDB["runtime.db"]
  end

  WEB --> REST
  REST --> CM
  REST --> ECM
  REST --> NBM
  CM --> SE
  SE --> DRV
  DRV --> CONN
  SE --> SI
  SI --> SC
  SC --> VSE
  SC --> WS
  SC --> SB_BRIDGE["ShadowBridge"]
  SB_BRIDGE --> PIPE["DataPipeline"]
  PIPE --> ECM
  PIPE --> NBM
  PIPE --> DSM
  ECM --> DRV
  NBM --> MQTT
  NBM --> OPCS
  NBM --> HTTP
  DSM --> RDB
  CM --> CDB
  REST -.->|读点优先| SC

1.3 核心组件一览

组件 路径 职责
ChannelManager internal/core/channel_manager.go 通道/设备/点位 CRUD、驱动生命周期、ScanEngine 任务注册、发现扫描 Job
AsyncJobManager internal/core/async_job.go 通道 Scan / 设备 Browse 异步任务(提交即返回 job_id,轮询取结果)
ScanEngine internal/core/scan_engine.go EDF 调度、断路器、防饿死、自适应 throttle
ExecutionLayer internal/core/execution_layer.go Serial / Parallel / Limited 执行 + 背压;共享链路 channelMu
ShadowCore internal/core/shadow_core.go 内存影子 SoT、COW 读、订阅通知
ShadowIngress internal/core/shadow_ingress.go 采集批量写入(缓冲 + 定时 flush)
ShadowBridge internal/core/shadow_bridge.go 影子变更 → Pipeline 扇出
DataPipeline internal/core/pipeline.go 异步批处理总线(每点最多缓冲 2 条)
VirtualShadowEngine internal/core/virtual_shadow_engine.go 公式派生虚拟设备
NorthboundManager internal/core/northbound_manager.go 北向客户端生命周期与推送;拓扑变更优先 SyncAddressSpace
ConnectionManager internal/driver/connection_manager.go 唯一 dial/重连 Owner;全局限流 10/s;可选绑定 channelMulinkMu
Drivers internal/driver/* 协议 ReadPoints / WritePoints;禁自建重连循环

2. 南向采集

2.1 采集流程(调度 → 执行 → 影子)

sequenceDiagram
  participant SE as ScanEngine
  participant EL as ExecutionLayer
  participant DRV as Driver
  participant SI as ShadowIngress
  participant SC as ShadowCore
  participant SB as ShadowBridge
  participant PL as DataPipeline

  loop 每个到期 ScanTask
    SE->>EL: Execute(task)
    alt Serial(Modbus / DLT645 / FINS / SLMP)
      EL->>EL: SerialQueue + channelMu
    else Parallel(OPC UA 等)
      EL->>EL: Backpressure.Allow
    else Limited(S7 / BACnet / ENIP)
      EL->>EL: 低并发 + 串行读
    end
    EL->>DRV: ReadPoints(points)
    DRV-->>EL: map[pointID]Value
    EL-->>SE: ExecuteResult
    SE->>SI: IngestDirect
    SI->>SC: ApplyShadowWrites
    SC->>SB: Subscribe 回调
    SB->>PL: PushBatch
    SE->>SE: updateTaskState / RTT / finalizeScanCollect
  end
阶段 行为 关键代码
注册 StartChannel 异步 Connect(不阻塞 API);成功后再 registerDeviceToScanEngine channel_manager.go
调度 最小堆 + wake timer + 10ms fallback;EDF 出队 scan_engine.go
执行 按协议模式分发;共享链路 channelMu唯一 I/O 互斥(与 REST withDriverIO 同一把) execution_layer.go
读点 Driver 无状态 ReadPoints;Gap/MTU 块读分片;ctx 可取消超时 internal/driver/*
写入 采集主路径经 ShadowIngress;REST 写点持 channelMu 后走驱动 shadow_ingress.go
UI 读 GetDevicePoints 禁止 live driver fallback,只读影子 channel_manager.go
状态 finalizeScanCollect:链路级 vs 设备级错误隔离 channel_device_state.go

2.2 连接、写入与重连

能力 实现
连接状态机 Disconnected → Connecting → Connected → Retrying → Dead( driver.ConnectionManager
重连 Owner 所有 dial 经 EnsureConnected / ScheduleReconnect;single-flight;Connecting 最小退避 200ms;无 deadline 的 ctx 在 Dead 冷却期快退(不冻死 API)
全局限流 MaxGlobalReconnectRate = 10/s,单一计数器在 driver/reconnect_limiter.gocore.ConnectionController 仅观测,不限流)
I/O∥重连 共享链路:ConnectionManager.SetLinkMutex(channelMu),dial/install 与 Scan/REST I/O 同锁;退避 sleep 持锁
SerialQueue 有界缓冲 + 背压(shared:{channelID});冒充链路互斥 Owner
写入 ChannelManager.withDriverIO / 边缘动作 → Driver WritePoint;成功后可回写影子
热配置 采集相关点位字段 → restartDeviceLocked;仅元数据(RW/名称等)→ 南向不停采,北向 SyncAddressSpace
发现扫描 POST .../scan 默认返回 202 + job_idGET /api/jobs/:id 轮询;?sync=1 保留同步兼容;OPC UA Browse job 超时 180s(驱动遵从 ctx 取消)

铁律(对齐 ScanEngine §5.3 / v5.2):Dial Owner = ConnectionManager;共享链路 I/O Owner = channelMu;观测层 = ConnectionController。Transport.mu 只保护连接指针,不包裹 Read/Write。

残留:OPC UA / EtherNet/IP 部分会话恢复仍在驱动内(见差距 G1);KNX/Profinet 个别路径仍直接调 CanRetry(计入全局限流槽,但 dial 入口未完全统一)。

2.3 协议差异对比

注册来源:cmd/main.go 空白导入。覆盖率见 南向驱动测试报告

协议 注册名 执行模式 读特点 发现/扫描(异步 Job) 备注
Modbus TCP/RTU modbus-tcp / modbus-rtu / modbus-rtu-over-tcp Serial Gap 块读合并;非法地址 24h SKIP 点位冷却 markPointFailed;BindLinkMutex
BACnet IP bacnet-ip Limited 对象属性读;故障隔离 Scan + ScanObjects(通道 45s / 设备 60s) 多设备隔离
OPC UA opc-ua Parallel 订阅 / 分批 Read Scan + ScanObjects(Browse ≤180s) 安全策略 + 凭证
Siemens S7 s7 Limited DB/M/I/Q 区域读 rack/slot
EtherNet/IP ethernet-ip Limited CIP Tag / Class2 属性 Logix Tag 路径
Omron FINS omron-fins Serial TCP/UDP 区读 源/目的节点
SNMP snmp GET/BULK ScanObjects v2c / v3 USM
IEC 104 iec60870-5-104 总召唤 + 自发 单点遥控 M1 已交付
DL/T645 dlt645 Serial 表地址 + DI 串口/TCP;BindLinkMutex
Mitsubishi SLMP mitsubishi-slmp Serial MC 3E frame/network/station
Profinet IO profinet-io 槽位 IO 可仿真
KNXnet/IP knxnet-ip 组地址 网关发现 TCP/UDP;BindLinkMutex
EtherCAT ethercat PDO + SDO PDO + SDO M1;可仿真主站

Scan Classfast100ms)/ normal(设备 Interval)/ slow10s)——每设备可注册多任务,故障点可 degrade_on_failure

发现 API(UI 默认异步)

POST /api/channels/:id/scan              → 202 { job_id, status: queued|running }
POST /api/channels/:id/devices/:dev/scan → 202 { job_id, status }
GET  /api/jobs/:jobId                    → { status, result?, error? }
DELETE /api/jobs/:jobId                  → 取消(ctx cancel)
# 兼容:?sync=1 或 Header X-Sync-Scan:1 走同步阻塞(CLI/旧脚本)

Fiber Read/WriteTimeout 保持短超时(30s/60s);长扫描不得靠拉长 HTTP 超时解决。


3. 影子设备模型

3.1 三类设备对照

类型 标识 数据来源 持久化 用途
真实影子 shadow-{deviceID} 南向 ReadPoints → Ingress 否(内存) UI/规则/北向统一读模型
物理设备 Channel.Devices 配置 DB + 驱动连接 config.db 采集目标与点位定义
虚拟影子 virtual-{id} 公式依赖真实影子 配置在 DB 跨设备聚合、派生指标
物理设备 (config) ──采集──► 真实影子 (ShadowCore) ──公式──► 虚拟影子
                              │
                              ├── WebSocket / REST(UI)
                              ├── ShadowBridge → Pipeline → 边缘规则 / 北向 / 历史
                              └── 通信画像(RTT / MTU / Gap)反哺调度与块读

3.2 生命周期

  1. 创建:设备注册到 ScanEngine 后,首次写入 Ingress 时惰性建立影子条目。
  2. 更新ApplyShadowWrites 批量 COW 更新;仅变更点进入 Notify。
  3. 通知:固定 worker pool(默认 6)hash 分区,有界扇出。
  4. 销毁:设备删除 / 通道停止时移除任务与影子条目。
  5. 重启恢复:影子不落盘;进程重启后由 ScanEngine 重新填充(配置仍在 config.db)。

3.3 时间语义

字段 含义
collected_at 驱动采集完成时间
updated_at 影子写入时间
timestamp 兼容字段,等同 collected_at

4. 数据热路径(最佳实践)

4.1 一句话路径

南向 ReadPoints → ShadowIngress 批量落地 ShadowCore → ShadowBridge 扇出 DataPipeline → 边缘规则 / 历史落库 / 北向上报;UI 经 WebSocket/REST 直读影子。

4.2 推荐落地步骤(Modbus 示例)

  1. 建通道:协议 modbus-tcp,配置 IP/Port/Timeout;执行模式自动为 Serial。
  2. 建设备与点位:Holding/Coil 地址规范化;合理设置 Interval 与 Scan Class。
  3. 启通道Connect + 注册 ScanTask;观察 GET /api/diagnostics/scan-engine
  4. 确认影子:UI 实时值或 REST 读点应命中影子(非驱动直读回退)。
  5. 挂边缘规则(可选):阈值/表达式订阅 Pipeline;写控走 DeviceIO。
  6. 挂北向:MQTT / Sparkplug / OPC UA Server / BACnet Server 映射影子点位;弱网依赖 NorthboundCache。
  7. 历史:设备存储策略 → DeviceStorageManager → runtime.db。

4.3 代码锚点

步骤 文件
启动装配 cmd/main.gowireShadowStack
调度执行 internal/core/scan_engine.go · execution_layer.go
Modbus 块读 internal/driver/modbus/scheduler.goreadGroup / markPointFailed
ENIP Tag internal/driver/ethernetip/scheduler.goprocessTagValue
影子写入 internal/core/shadow_ingress.go · shadow_core.go
扇出 internal/core/shadow_bridge.go · pipeline.go
边缘 internal/core/edge_compute_manager.go
北向 internal/core/northbound_manager.go · internal/northbound/*
历史 internal/core/device_storage_manager.go · internal/storage/*

4.4 反模式(避免)

  • 绕过 Shadow 直接让北向/规则订阅驱动回调(数据面分裂)。
  • 在 Driver 内自建 ticker / 无限重试(破坏 ScanEngine SLA)。
  • 无背压地向 Pipeline 同步阻塞推送(拖死采集环)。
  • 把影子当历史库(影子仅内存;历史走 runtime.db / 北向缓存)。
  • 同步 HTTP 跑长扫描/Browse(阻塞 UI 线程、撞 Fiber WriteTimeout);必须走 /api/jobs 轮询。
  • 拓扑变更就 Stop+Start 北向 OPC UA / BACnet Server(断客户端会话);仅端口/安全策略/证书/DeviceID 等结构性变更才重建 listener。
  • ConnectionController 发起 dial 或另造全局限流(双 Owner);限流只走 reconnect_limiter.go

5. 关联关系整体网络

flowchart LR
  PHY["物理设备配置"] --> SCAN["ScanEngine 采集"]
  SCAN --> REAL["真实影子 ShadowCore"]
  REAL --> VIRT["虚拟影子"]
  REAL --> UI["UI REST/WS"]
  REAL --> BR["ShadowBridge"]
  BR --> EDGE["边缘计算"]
  BR --> HIST["持久化 runtime.db"]
  BR --> NB["北向映射/上报"]
  EDGE -->|写控| PHY
  NB -->|订阅写点| PHY
  REAL -.->|RTT/MTU/Gap| SCAN
  PHY -->|拓扑防抖 400ms| NB_SYNC["OPC UA SyncAddressSpace"]
链路 说明
影子 ↔ UI Subscribe → WebSocket;getDevicePoints 影子(禁 live fallback)
影子 ↔ 边缘 Bridge → Pipeline → EdgeComputeManager.handleValue
影子 ↔ 持久化 Pipeline → Storage / DeviceStorageManager
影子 ↔ 北向 Pipeline → NBM;OPC UA Server GetShadowPoint;BACnet Server Update(v Value);拓扑 → 原地 Sync(结构性变更才停服)
虚拟 ↔ 真实 VirtualShadowEngine 依赖图增量计算
画像 ↔ 调度 RTT 自适应降速;Gap/MTU 影响块读分片

6. 工业级稳定性设计

6.1 基线机制

维度 机制 位置
超时 驱动/连接 timeout;executeParallel / Serial 外层可取消;Fiber 短超时 Driver + ExecutionLayer + server.go
背压 Parallel:全局 512 / 单设备 8 / 限速;Pipeline 每点缓冲 ≤2 backpressure_controller.go · pipeline.go
隔离 每设备 Circuit Breaker;Serial 队列 + channelMu;链路/设备错误分离 circuit_breaker.go · channel_device_state.go
防饿死 300s rescue + EDF miss 提权 scan_engine.go
抖动 hard jitter clamp;统计 SLA P95/P99 scan_engine_edf*
幂等/合并 Ingress 批量 apply;Pipeline 同点覆盖旧值 ShadowIngress · DataPipeline
点位降级 故障 Tag 冷却,不拖死同批 point_degradation_manager.go · Modbus SKIPPED
快照恢复 配置强一致落盘(CRUD 异步 save,不占 cm.mu);影子重启后由采集重建 config.db + ScanEngine
断网补发 NorthboundCache / Store&Forward store_forward.go · northbound cache
可观测 diagnostics · sla_warnings · 结构化日志 · UI 面板 scan_engine_metrics.go

统计 SLA 门控(x86 mock,≤10k tag):lag P95 <100ms · drift <50ms · miss=0(稳态)· GC pause <20ms。2026-07-12 Mac 复测:lag P95 1.56ms · miss=0 · drift 0.00% · G007 986 设备/s — 见 Q3 万 Tag 压测报告 · 压力测试报告。运维巡检见 SLA 运维手册

6.2 2026-07 稳定性加固(已落地)

对齐 ScanEngine 重构方案 §5.3 与 v5.2 补丁:单一 Owner、单一链路互斥、API 不阻塞。

P0 / P1(此前已合入)

行为变化 关键路径
Transport 去锁退避 Connect / 重连 dial 跨 dial 持 Transport.mu;避免离线从站冻住同通道 internal/driver/*/transport.go
REST ↔ Scan 共享 channelMu withDriverIO 与 ExecutionLayer 同一把锁,消除写/读半开竞态 channel_manager.go · execution_layer.go
GetDevicePoints 禁 live UI 读点超时不再打驱动,只读 ShadowCore channel_manager.go
执行可取消超时 Parallel 执行尊重 ctx;Serial 外层超时封顶(防慢从站占槽) execution_layer.go
StartChannel 异步 Connect 启动/启通道不在请求线程 dial;失败走 ScheduleReconnect channel_manager.go
CRUD 异步落盘 saveChannels 拷贝后 goroutine 写 bbolt,不长时间持 cm.mu channel_manager.go
拓扑 400ms 防抖 notifyTopologyChange 合并突发 CRUD,再通知北向 channel_manager.go
Shadow notify 有界池 固定 worker + 有界队列,禁止无界 go 扇出 shadow_notify_pool.go
Fiber / EnsureConnected HTTP Read 30s / Write 60s;无 deadline 的 EnsureConnected 在 Dead 冷却快退 server.go · connection_manager.go

本轮三项(2026-07-15)

行为变化 关键路径
扫描 / Browse → 异步 Job POST .../scan → 202 job_idGET /api/jobs/:id;驱动遵从 job ctx;UI postScanAndWait 轮询 async_job.go · async_job_handler.go · ui/src/utils/asyncJob.js
北向 OPC UA 增量 Node 拓扑 / 设备映射 → SyncAddressSpace(Delete Gateway 树 + 原地重建 Node,不停 TCP listener);仅 Port / Security / Endpoint / 证书 / Auth 触发 Stop+Start northbound/opcua/server.go · RebuildOPCUAServers
北向 BACnet Server 热更新 设备/点位映射变更 → SyncAddressSpace(仅重建地址空间,不停 UDP 监听);仅 IP/Port/DeviceID/MaxPDU/SubnetCIDR 触发 Stop+Start northbound/bacnet/server.go · UpdateConfig
全局限流单一 Owner reconnect_limiter.go 唯一 10/s 槽;共享链路 BindLinkMutex(channelMu) 闭合 I/O∥reconnect;文档 §5.3 双计数器表述已订正 reconnect_limiter.go · Modbus/DLT645/KNX BindLinkMutex

北向 OPC UA 决策树:

拓扑变更 / SyncOPCUAServer / Devices 映射变更
        │
        ▼
  SyncAddressSpace()     ← 默认路径:不停服、会话不断
        │
结构性变更?Port · Endpoint · SecurityPolicy/Mode · Cert · AuthMethods/Users
        │ yes
        ▼
  UpdateConfig → Stop + Start(重建 listener)

北向 BACnet Server 决策树:

设备/点位映射变更 / SyncAddressSpace
        │
        ▼
  SyncAddressSpace()     ← 默认路径:不停服、UDP 监听不断
        │
结构性变更?IP · Port · DeviceID · MaxPDU · SubnetCIDR · Interface
        │ yes
        ▼
  UpdateConfig → Stop + Start(重建 listener)

---



## 7. 启动与配置闭环(简表)


| 阶段   | 入口                            | 结果                                                                 |
| ---- | ----------------------------- | -------------------------------------------------------------------- |
| 安装   | `/api/install/*`              | 创建 config.db(Users/System/Server)                                  |
| 启动   | `cmd/main.go`                 | Pipeline → CM+ScanEngine → ShadowCore → NBM → Server;拓扑 handler 绑 Sync |
| CRUD | REST → Manager → 异步 `SaveConfig` | 热更新内存 + 后台写 DB(不阻塞请求)                                           |
| 运行   | ScanEngine 周期                 | 影子填充 → Bridge 扇出                                                   |
| 发现   | Scan/Browse Job               | 202 → 轮询;不占用 Fiber 长 WriteTimeout                                   |


```text
API → Manager 内存更新 → saveFunc(async) → ConfigManager.SaveConfig → config.db
拓扑 CRUD → debounce 400ms → RebuildOPCUAServers → SyncAddressSpace(多数)/ Stop+Start(结构性)

8. 已知差距与演进

ID 状态
G1 OPC UA / ENIP 会话恢复未完全迁入 ScheduleReconnect 进行中
G1b KNX/Profinet 自管 dial 循环仍吞限流槽(应改走 EnsureConnected) 待收口
G2 各协议 24h/72h 联机报告未全覆盖 Phase 2
G3 ARMv7 板端 P99 复验 脚本就绪
G4 Scan Class UI 全面暴露 代码已支持
G-scan 同步 Scan/Browse 阻塞 UI 已关闭(异步 Job)
G-nb-opc 北向拓扑全量 Stop+Start 已关闭(SyncAddressSpace)
G-limit driver/core 双全局限流 已关闭(单一 Owner)
G5+ Tag 数据库抽象、冗余 Failover、北向独立限频 Q4 规划

路线图见 ROADMAP;发布门禁见 RELEASE_GATE;重连规范见 ScanEngine 重构方案 §5.3


9. 文档与测试索引

  • 驱动能力:drivers/index.md · index_en.md
  • 热路径单测:internal/core/channel_manager_hotpath_test.gointernal/driver/modbus/scheduler_hotpath_test.gointernal/driver/ethernetip/process_tag_test.go
  • 异步 Job:internal/core/async_job_test.go · GET /api/jobs/:jobId
  • 北向原地同步:internal/northbound/opcua/server_sync_test.go
  • 重连 linkMu:internal/driver/connection_manager_linkmu_test.go
  • 集成:internal/core/shadow_pipeline_integration_test.go
  • 测试报告:testing/南向驱动测试报告.md