边缘网关架构设计总览
工程铁律: 任何性能优化不得以牺牲稳定性为代价;任何架构优化不得增加系统恢复复杂度。
本文档是 edgeCore 运行时架构权威说明,以 影子设备(ShadowCore) 为数据枢纽,梳理南向采集 → 影子 → 边缘计算 / 持久化 / UI → 北向上报的完整热路径。
| 相关文档 | 说明 |
|---|---|
| 影子设备设计 | Shadow 模型与 COW / Ingress |
| 影子设备系统联动关系 | 组件关联细节 |
| 南向驱动矩阵 | 协议能力与覆盖率 |
| 产品说明 / PRODUCT | 能力与价值 |
| 用户手册 | 部署与操作 |
| ScanEngine 重构方案 | 调度内核规范 |
1. 系统总览
1.1 设计原则
- 配置唯一源:
data/config.db(bbolt);运行时历史与缓存在data/runtime.db。 - 运行时真源:纯内存 ShadowCore;REST / WebSocket 只读影子(禁止 live driver fallback)。
- 调度驱动采集:ScanEngine 统一时间、资源、执行与任务状态;Driver 为纯执行(禁止内部 ticker / 自建重连循环)。
- 统一扇出:ShadowBridge → DataPipeline 承载边缘规则、北向推送、历史落库。
- 统计 SLA 可观测:
GET /api/diagnostics/scan-engine+sla_warnings+ UI 通道监控。 - 长操作不阻塞管理面:设备发现 / OPC UA Browse 走异步 Job;北向地址空间默认原地 Sync;共享链路 I/O 与重连统一
channelMu。
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;可选绑定 channelMu(linkMu) |
| 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.go(core.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_id;GET /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 Class:fast(100ms)/ normal(设备 Interval)/ slow(10s)——每设备可注册多任务,故障点可 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 生命周期
- 创建:设备注册到 ScanEngine 后,首次写入 Ingress 时惰性建立影子条目。
- 更新:
ApplyShadowWrites批量 COW 更新;仅变更点进入 Notify。 - 通知:固定 worker pool(默认 6)hash 分区,有界扇出。
- 销毁:设备删除 / 通道停止时移除任务与影子条目。
- 重启恢复:影子不落盘;进程重启后由 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 示例)
- 建通道:协议
modbus-tcp,配置 IP/Port/Timeout;执行模式自动为 Serial。 - 建设备与点位:Holding/Coil 地址规范化;合理设置 Interval 与 Scan Class。
- 启通道:
Connect+ 注册 ScanTask;观察GET /api/diagnostics/scan-engine。 - 确认影子:UI 实时值或 REST 读点应命中影子(非驱动直读回退)。
- 挂边缘规则(可选):阈值/表达式订阅 Pipeline;写控走 DeviceIO。
- 挂北向:MQTT / Sparkplug / OPC UA Server / BACnet Server 映射影子点位;弱网依赖 NorthboundCache。
- 历史:设备存储策略 →
DeviceStorageManager→ runtime.db。
4.3 代码锚点
| 步骤 | 文件 |
|---|---|
| 启动装配 | cmd/main.go(wireShadowStack) |
| 调度执行 | internal/core/scan_engine.go · execution_layer.go |
| Modbus 块读 | internal/driver/modbus/scheduler.go(readGroup / markPointFailed) |
| ENIP Tag | internal/driver/ethernetip/scheduler.go(processTagValue) |
| 影子写入 | 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_id;GET /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 全面暴露 | 代码已支持 |
| 已关闭(异步 Job) | ||
| 已关闭(SyncAddressSpace) | ||
| 已关闭(单一 Owner) | ||
| G5+ | Tag 数据库抽象、冗余 Failover、北向独立限频 | Q4 规划 |
路线图见 ROADMAP;发布门禁见 RELEASE_GATE;重连规范见 ScanEngine 重构方案 §5.3。
9. 文档与测试索引
- 驱动能力:drivers/index.md · index_en.md
- 热路径单测:
internal/core/channel_manager_hotpath_test.go、internal/driver/modbus/scheduler_hotpath_test.go、internal/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