批量合并发生在什么阶段
当 Channel 以 Polling 模式轮询采集时,驱动会对每个 Device 的可读点位做批量规划,尽可能把多个点位合并成更少的 Modbus 请求。这个过程完全在内存中进行(Planner 模块),不产生网络 I/O。
合并算法
对输入点位集合:
- 分组:按
functionCode分组(例如 Holding Registers 和 Input Registers 是物理隔离的区域,无法合并)。 - 排序:组内按
address升序排序。 - 贪婪合并:从低地址开始扫描,尝试把相邻的点位“吸纳”进当前 Batch:
- Gap 检查:
(next_point.start - current_batch.end) <= maxGap* - Span 检查:
(next_point.end - current_batch.start) <= maxBatch*
- Gap 检查:
如果两个条件都满足,则合并,批次的 end 扩展到 max(current_batch.end, next_point.end)。
合并后,驱动会:
- 发送一个读请求(起始地址 + quantity=span)。
- 收到响应后,在内存中根据每个点位的
address和quantity从大块数据中零拷贝切片(Slicing)并解码。
maxGap* / maxBatch* 如何设置
- 寄存器读:
maxBatchRegisters(单位:word)、maxGapRegisters(单位:word)- 协议硬上限:一次读寄存器最多 125 words,驱动会对
maxBatchRegisters做 clamp
- 协议硬上限:一次读寄存器最多 125 words,驱动会对
- bit 读:
maxBatchBits(单位:bit)、maxGapBits(单位:bit)- 协议硬上限:
maxBatchBits <= 2000
- 协议硬上限:
一句话
maxGap*控制“允许跨越多少空洞”maxBatch*控制“一次请求总 span 多大”。
1) 吞吐优先(地址密集、设备可靠)
- 寄存器(0x03/0x04):
maxGapRegisters:1~10maxBatchRegisters:100~125
- bit(0x01/0x02):
maxGapBits:200~500maxBatchBits:512~2000
效果:
- 请求次数最少,总线利用率最高(减少了协议头开销)。
- 适合 Modbus TCP 或波特率较高的 RS-485 环境。
风险:
- 如果设备中间有“非法地址”且正好落在 Gap 中,部分老旧设备可能会返回 Exception 导致整个 Batch 失败。
- 单次响应包较大,对抗干扰能力稍弱。
2) 稳定优先(链路抖动、设备偶发超时)
- 寄存器(0x03/0x04):
maxGapRegisters:0(禁止跨越空洞)maxBatchRegisters:40~80
- bit(0x01/0x02):
maxGapBits:0(禁止跨越空洞)maxBatchBits:128~512
效果:
- 只有连续定义的点位才合并。
- 单次失败影响范围小,重试成本低。
- 适合长距离 RS-485 线、干扰严重的现场。
3) RS-485 多从站(共享总线)
RS-485 的瓶颈往往是总线时分与从站响应时间,而不是 CPU:
- 不要盲目拉大
maxBatch*:过大的包会占用总线过久,增加误码率。 - 增大采集周期 (
period):给总线“喘息”时间。 - 配置
connection_policy:read_timeout_ms: 至少设置为(预期传输时间 + 设备处理时间) * 1.5。backoff: 设置退避策略,避免多个设备同时掉线后“惊群重试”彻底堵死总线。
RTU 特别提醒
在 RTU/RS-485 场景,驱动会强制单连接(单飞),因此 tcpPoolSize 不会带来并发收益。吞吐优化优先从 批量规划 与 采集周期 入手。
常见问题排查
1) “点位都超时/偶尔全失败”
优先排查:
read_timeout_ms太小(串口/设备响应慢)。- 从站实际
slaveId与配置不一致。 - RS-485 现场接线/终端电阻/偏置电阻/屏蔽接地问题。
2) “吞吐低,请求太多”
优先排查:
- 点位
functionCode分散(同一物理区尽量统一用寄存器区)。 - 单点
quantity太大导致合并被阻断(建议拆分)。 maxGapRegisters/maxGapBits设为了 0,导致地址即使只差 1 也无法合并。
