145 lines
5.5 KiB
Markdown
145 lines
5.5 KiB
Markdown
# 数据流转
|
||
|
||
## 1. 概念约定
|
||
|
||
- **物理设备**:真实硬件端口(当前主要是串口),由 `SerialQtDriver` 直接读写。
|
||
- **设备实例**:`SerialDevice`,负责单设备生命周期、驱动回调、上下行数据接入。
|
||
- **原始总线消息**:`RawBusMessage`,包含 `endpoint`、`protocolHint`、`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`
|
||
- `endpoint = 串口名`
|
||
- `protocolHint = DeviceInfo.protocol`
|
||
- `payload/payloadSize = 原始字节数据`
|
||
- `traceId = serial:<endpoint>:<timestamp>`
|
||
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)**
|
||
- 按 `endpoint` 创建/复用独立 `EndpointFramingState`(每端点一个分帧线程 + chunk 队列)。
|
||
- chunk 先进入端点队列,端点线程负责分帧并赋值 `frameSeq`。
|
||
- 完整帧进入 `m_framedQ`,由解析线程池处理。
|
||
- **单线程模式(parallelPipeline=false)**
|
||
- 直接进入 `m_ingressQ`,由单 worker 串行处理。
|
||
|
||
### 3.2 Stage 1:分帧(Framer)
|
||
|
||
`dispatchFramer(...)` 的选择策略:
|
||
|
||
1. 优先根据 `protocolHint` 从插件管理器选择协议分帧插件。
|
||
2. 按 `endpoint + pluginId` 复用 framer session(保持协议状态机连续性)。
|
||
3. 若没有可用插件,降级到 `PassthroughFramer`(透传/整包策略)。
|
||
|
||
输出:`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**
|
||
- 应用动态规则(scale/offset/unit)。
|
||
- 写入 trace,补充富化字段。
|
||
|
||
说明:当前 `runStages234` 只返回布尔结果,`EnrichedMessage` 由内部生成,后续可接持久化/发布环节。
|
||
|
||
### 3.4 出口与有序保证
|
||
|
||
- 未开启有序出口:解析成功后直接进入 `m_egressQ`。
|
||
- 开启 `orderedEgress`:
|
||
- 解析线程先写入 `m_mergeQ`。
|
||
- `mergerLoop` 按 `endpoint + frameSeq` 重排。
|
||
- 使用 `nextExpected` 保证同端点有序输出。
|
||
- 超过 `maxReorderDepth` 的乱序缓存上限会丢弃并计数。
|
||
|
||
---
|
||
|
||
## 4. 错误与背压点
|
||
|
||
主要失败点及表现:
|
||
|
||
1. **内存池分配失败**:`SerialDevice::onRead` 直接 `reportError(memory pool exhausted)`。
|
||
2. **入口队列满**:`enqueueIngress` 返回 false,计入 drop。
|
||
3. **分帧/解析插件缺失或失败**:对应帧在阶段中被丢弃。
|
||
4. **过滤/校验失败**:消息跳过,不进入下游。
|
||
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)
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 下行(现状)
|
||
|
||
- `RawBusMessage.direction` 已支持 `Downstream` 语义。
|
||
- `SerialDevice` 已提供 `writePayload(...)` 写入能力。
|
||
- 当前代码中 egress 路由到物理设备的绑定仍预留(`SerialDeviceManager` 里相关逻辑已注释),后续可补齐:
|
||
- egress 消息 -> `EgressRouter` -> endpoint 设备 -> `writePayload`。
|
||
|
||
---
|
||
|
||
## 7. 维护建议
|
||
|
||
1. `traceId` 建议在全链路透传到最终发布侧,便于定位帧级问题。
|
||
2. 为 Stage2/Stage3/Stage4 增加细粒度指标(成功率、耗时、drop 原因)。
|
||
3. 文档后续可新增“协议插件开发约定”章节(framer/parser/mapper 三类插件接口对照)。 |