Files
softbus_daemon/docs/数据流转.md
flower_linux 7280fca08d dommessage
2026-04-28 21:56:56 +08:00

207 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 数据流转
## 1. 概念约定
- **物理设备**:真实硬件端口(当前主要是串口),由 `SerialQtDriver` 直接读写。
- **设备实例**`SerialDevice`,负责单设备生命周期、驱动回调、上下行数据接入。
- **原始总线消息**`RawBusMessage`,包含 `endpointHash``protocol``routingKey``header``payload``traceId``direction` 等基础字段。
- **流水线上下文**`PipelineContext`,继承 `RawBusMessage`,增加 `frameKind``frameSeq` 等管道字段。
- **业务消息**:经过协议解析和映射后得到 `DOMMessage`(统一语义数据)。
---
## 2. 上行主链路(串口 -> 消息总线)
### 2.1 设备初始化
`SerialDeviceManager` 完成以下动作:
1. 根据配置创建并配置 `SerialQtDriver`(波特率、校验、停止位、流控)。
2. 创建 `SerialDevice` 并注入 `DeviceInfo`(含协议提示,如 `modbus_rtu`)。
3. 通过 `bindIngress(...)` 绑定:
- `IIngressPort`(当前实现为 `DriverIngressAdapter`
- `MemoryPool`(用于 payload 内存块分配)
4. 调用 `start()` 打开端口并注册读回调/错误回调。
### 2.2 读回调进入 ingress
当串口收到字节流时,`SerialDevice::onRead(...)` 执行:
1.`MemoryPool` 分配内存块并拷贝原始字节。
2. 构造 `RawBusMessage`
- `direction = Upstream`
- `endpointHash = makeEndpointHash(串口名)`
- `protocol = protocolTypeFromHint(DeviceInfo.protocol)`
- `routingKey = makeRoutingKey(endpointHash, deviceId)`
- `header = ProtocolHeader{}`(由后续 framer/parser 按协议填充 OOB 元数据)
- `payload = 原始字节数据`(长度由 `payload.length` 表示,不再维护 `payloadSize`
- `traceId = serial:<endpoint>:<timestampMs>`
3. 调用 `m_ingress->push(std::move(ctx))` 进入统一入口。
4. 若内存池不足或 ingress 队列满,通过 `reportError(...)` 记录错误。
### 2.3 ingress 适配为 PipelineContext
`DriverIngressAdapter::push(...)` 负责轻量转换:
1. 新建 `PipelineContext`
2. `pipelineCtx.raw() = std::move(ctx)` 将原始消息搬运进管道上下文。
3. 调用 `PipelineEngine::enqueueIngress(...)` 入总线处理队列。
---
## 3. PipelineEngine 内部流转
### 3.1 入口分流
`enqueueIngress(...)` 分两种模式:
- **并行模式parallelPipeline=true**
-`endpointHash` 创建/复用独立 `EndpointFramingState`(每端点一个分帧线程 + chunk 队列)。
- chunk 先进入端点队列,端点线程负责分帧并赋值 `frameSeq`
- 完整帧进入 `m_framedQ`,由解析线程池处理。
- **单线程模式parallelPipeline=false**
- 直接进入 `m_ingressQ`,由单 worker 串行处理。
### 3.2 Stage 1分帧Framer
`dispatchFramer(...)` 的选择策略:
1.`protocol``ProtocolType`)从插件管理器选择协议分帧插件。
2. 按结构化键 `SessionKey{endpointHash, pluginId}` 复用 framer/parser/mapper session保持协议状态机连续性
3. 若没有可用插件,降级到 `PassthroughFramer`(透传/整包策略)。
4. framer session 创建接口已升级为 `createFramerSession(uint32_t endpointHash, ...)`,不再传递 endpoint 字符串。
输出:`frameKind = CompleteFrame` 的帧批次。
### 3.3 Stage 2/3/4解析 -> 过滤 -> 校验(当前返回语义)
`runStages234(...)` 核心流程:
1. **Stage2 Parser**
- 选择 `IProtocolPlugin`,复用 parser session。
- `parse(rawCtx, envelope)` 产出协议封装 `ProtocolEnvelope`
- 选择 mapper 插件并映射为 `std::vector<DOMMessage>`
2. **Stage3 Filter**
-`DOMMessage` 执行过滤规则(`ProtocolParseFilter::process`)。
3. **Stage4 Validator**
- 最低校验当前为 `messageId` 非空。
4. **Transform/Enrich**
- 仅对通过 filter+validator 的 `DOMMessage` 应用动态规则scale/offset/unit
- 规则作用域按 `endpointHash/deviceId/stableKey` 匹配,不再按 endpoint 字符串匹配。
- 写入 trace补充富化字段。
说明(与代码一致):
- `runStages234``stage2Parser` 返回空时才返回 `false`
- 只要 `stage2Parser` 有消息,哪怕全部在 Stage3/Stage4 被跳过,当前实现仍返回 `true`
- `EnrichedMessage` 在内部生成,但尚未进入独立发布/持久化通道。
### 3.4 出口与有序保证
- 未开启有序出口:解析成功后直接进入 `m_egressQ`
- 开启 `orderedEgress`
- 解析线程先写入 `m_mergeQ`
- `mergerLoop``endpointHash(映射键) + frameSeq` 重排。
- 使用 `nextExpected` 保证同端点有序输出。
- 超过 `maxReorderDepth` 的乱序缓存上限会丢弃并计数。
---
## 4. 错误与背压点
主要失败点及表现(按当前实现):
1. **内存池分配失败**`SerialDevice::onRead` 直接 `reportError(memory pool exhausted)`
2. **入口队列满**`enqueueIngress` 返回 false计入 drop。
3. **分帧/解析/映射插件缺失或失败**`stage2Parser` 返回空,`runStages234=false`,该帧不进入 egress。
4. **过滤/校验失败**:对应 `DOMMessage` 被跳过(不做 enrich但该 `PipelineContext` 仍可能继续进入 egress只要 Stage2 非空)。
5. **重排深度超限**`mergeDrop` 增加并告警。
---
## 5. 当前链路时序(上行)
```text
SerialQtDriver(read callback)
-> SerialDevice::onRead
-> MemoryPool::allocate + RawBusMessage
-> IIngressPort::push (DriverIngressAdapter)
-> PipelineEngine::enqueueIngress
-> [Framer] dispatchFramer/feedChunk
-> [Stage2] parse + map -> DOMMessage
-> [Stage3] filter
-> [Stage4] validate
-> transform/enrich
-> egressQ (有序模式下先 merge 再 egress)
-> DeviceBusManager::processEgressLoop
-> EgressRouter::send
-> SerialDevice::writePayload
```
---
## 6. egress 到设备(现状)
- `DeviceBusManager` 已启动 `processEgressLoop`,持续从 `PipelineEngine::tryPopEgress` 取数据并调用 `EgressRouter::send`
- `EgressRouter` 已实现基于 `endpointHash``SerialDevice` 查找和 `writePayload(...)` 发送。
- 当前关键缺口:`SerialDeviceManager::initializeSerialDeviceWithDeps``bindSerialDevice(...)` 仍被注释,导致路由表通常为空,`send` 多数返回失败。
- 因此当前链路是“egress 框架已存在,但默认没有绑定到串口实例”。
---
## 7. 维护建议
1. `traceId` 建议在全链路透传到最终发布侧,便于定位帧级问题。
2. 为 Stage2/Stage3/Stage4 增加细粒度指标成功率、耗时、drop 原因)。
3. 文档后续可新增“协议插件开发约定”章节framer/parser/mapper 三类插件接口对照)。
---
## 8. 无规则冷启动与发现池(新增)
### 8.1 设计目标
- 默认保持极致性能:**未命中映射规则的寄存器不会产出 DOMMessage**。
- 仅在配置页面开启时进入“嗅探模式Sniffer Mode将未命中点位以低成本写入内存发现池。
- 发现池只维护“最近值”,不做全量历史存储,避免高频总线拖垮系统。
### 8.2 Mapper 行为Modbus RTU
`ModbusRtuProtocolMapperPlugin` + `PipelineEngine``IMappingLookup` 组合行为:
1.`ReadHoldingRegistersRequest` 记录 `unitId -> startAddress`,用于后续响应地址回填。
2.`ReadHoldingRegistersResponse` 遍历寄存器并构造 `compositeKey`
- `compositeKey = endpointHash + protocol + slaveId + registerAddress`(位拼接)。
3. **解析优先级(统一语义)**`MappingRegistry`(运行时绑定)优先;未命中则查 `ProfileRegistry``device_profiles.json`,按 `runtimeDeviceId``modbus_rtu` 协议匹配,支持 `*` 通配设备)。
4. 若上述之一命中:
- 生成轻量 `DOMMessage``messageId`/`pointId` 来自绑定或 profile`metadataId` 来自 profile若有`value` 为工程值;静态元数据不随每条样本重复序列化,由 `metadataId` 在控制面/Registry 解析。
5. 若仍未命中:
- 若存在 `discoverySink`,始终上报 `DiscoveryRecord`(发现池内部仍受嗅探开关与限频约束);
- **仅当嗅探开启**时,主链路额外产出 `modbus.unmapped:*``DOMMessage` 供规则调试;嗅探关闭时不产出未映射 DOM减少总线冗载。
### 8.3 DiscoveryPool 约束
- 数据结构:`unordered_map<uint64_t, DiscoverySample>`
- 样本内容:`compositeKey/endpointHash/protocol/slaveId/registerAddress/rawValue/firstSeenMs/lastSeenMs/hitCount`
- 性能保护:
- `maxEntries` 限制最大条目数(超限按最旧 `lastSeenMs` 淘汰);
- `minSampleIntervalMs` 做按键限频,降低写放大;
- 支持 `ttlMs` 自动关闭嗅探,防止遗留长期开启。
### 8.4 REST API配置工作台
- `POST /api/v1/discovery/start`:开启嗅探(可传 `ttlMs`)。
- `POST /api/v1/discovery/stop`:关闭嗅探。
- `GET /api/v1/discovery/status`:查看嗅探状态与池容量信息。
- `GET /api/v1/discovery/samples`:拉取发现样本(支持 `offset/limit/endpointHash/protocol`)。
- `POST /api/v1/discovery/clear`:清空发现池。
- `POST /api/v1/mapping/bind`:新增或更新绑定规则(`compositeKey -> domPath + scale/offset/unit`)。
- `POST /api/v1/mapping/unbind`:删除绑定规则。
- `GET /api/v1/mapping/list`:查看当前绑定规则。
### 8.5 双向匹配工作流
1. Vue 打开配置页 -> 调用 `discovery/start`
2. 左侧“发现列表”轮询 `discovery/samples`
3. 用户执行 Bottom-Up从样本创建 DOM 绑定)或 Top-Down将样本拖到 DOM 占位符)。
4. 前端调用 `mapping/bind`,规则即时生效。
5. 下一帧同 `compositeKey` 数据在 mapper 命中,进入 DOM 主链路;该点位在发现列表中自动消失(查询时过滤已映射键)。