4. 断点续传机制原理与关键要素

矿山物联网的数据链路与地面机房最大的不同,在于“断”是常态而非异常:采掘工作面推进导致网关频繁搬迁、井下环网光纤被机械损伤、罐笼与井筒间的无线链路周期性遮挡、4G/5G 专网在巷道拐弯处出现盲区。如果采集端一旦检测到链路中断就丢弃数据,或者恢复后从当前时刻重新开始上报,那么每一次掉线都会在监测曲线上留下一个无法解释的空洞。断点续传要解决的正是这个问题:让数据在链路不可用时先落到本地,链路恢复后从“断点”继续补传,并保证补传的数据与实时数据在服务端能够正确拼接、不重不漏、顺序可辨。

需要先明确一个基本认识:断点续传不是单一技术,而是由数据缓存、断点标记、重传确认、去重与顺序保证四个环节咬合而成的机制链。任何一个环节缺失,都会导致“看起来补传了,实际数据仍不可用”。下面结合矿山场景逐一展开。

断点续传机制链:采集端 → 缓存 → 断点标记 → 重传确认 → 服务端去重与排序 采集端 传感器/分站 生成 seq + ts ① 数据缓存 环形队列 / 本地库 容量与保留时长受限 ② 断点标记 last_ack_seq 持久化 掉电不丢、重启可读 ③ 重传确认 批量补传 + ACK 超时重发、窗口控制 ④ 去重与顺序 按 (设备,seq) 幂等 按 ts 排序入库 补传数据流 ACK 推进 last_ack_seq 矿山链路中断的典型诱因 网关搬迁 · 光纤机械损伤 · 巷道盲区 · 井筒遮挡 → 触发本地缓存与断点记录,而非丢弃数据 矿山场景下的实现约束 存储受限 分站 Flash 小、擦写寿命 有限,需环形覆盖策略 并明确“最早可补传点” 时钟与序号 断网期间无法对时 以单调 seq 为主键 ts 仅作辅助排序 带宽与窗口 恢复瞬间易拥塞 限速补传、分批 ACK 实时流优先于补传流 幂等与去重 ACK 丢失导致重复发送 服务端按唯一键覆盖写 保证“至少一次”变“恰好一次”

数据缓存:先落盘,再谈补传

缓存是断点续传的物质基础。采集端在发送前必须先把数据写入本地持久化存储,形成“先写后发”的顺序,否则链路中断瞬间正在发送的那条数据就会永久丢失。矿山分站通常使用小容量 Flash 或 FRAM,容量从几百 KB 到几 MB 不等,因此缓存不能无限增长,一般采用环形队列:写满后覆盖最旧数据。这就带来一个必须明确的边界——最早可补传点。例如某分站以 1 秒 1 条的频率采集 8 个测点,单条记录约 64 字节,1 MB 可用空间约可保存 2000 条左右,即约 33 分钟的断网数据;超过这个时长,最老的数据会被覆盖,补传只能从环形队列的队首开始。工程上应把这个“可补传时长”作为设计指标写进方案,并在网管界面上暴露“缓存水位”和“最早可补传时间”,让运维人员知道断网过久会丢哪一段。

另一个容易被忽视的点是写放大与寿命。高频小记录逐条写 Flash 会加速磨损,实际实现常采用“内存聚合 + 批量落盘”,例如每 20 条或每 5 秒刷一次,同时配合掉电检测电容保证刷盘完成。缓存记录应包含设备标识、单调递增序号 seq、采集时间 ts、测点值和质量码,这四类字段是后续断点、去重、排序的共同依据。

断点标记:让“从哪继续”可恢复

断点标记的本质是持久化一个“已确认位置”。最常用的做法是维护 last_ack_seq:服务端每确认收到一批数据,就把该批最大 seq 回传,采集端收到后更新并持久化 last_ack_seq。链路恢复后,采集端从 last_ack_seq + 1 开始扫描本地缓存并补传。这里的关键约束是掉电不丢、重启可读:last_ack_seq 必须写在非易失存储中,且更新要与数据落盘保持一致的顺序,避免出现“数据已删但断点未更新”或“断点已更新但数据未落盘”的不一致。

矿山现场还有一个现实问题:断网期间设备可能重启,甚至更换分站。因此断点标记不能只依赖内存变量,也不能只依赖服务端单方面记录。较稳妥的做法是双向冗余——采集端持久化 last_ack_seq,服务端同时记录每个设备已入库的最大 seq;恢复连接后先做一次“断点协商”,取两者中较小值作为补传起点,宁可少量重传,也不留空洞。

重传确认:补传要可控,不能冲击实时流

链路恢复的瞬间往往是最危险的时刻:积压了数十分钟的数据如果全速倾泻,会挤占本就有限的井下上行带宽,导致实时数据也被延迟,形成“补传把实时流堵死”的次生问题。因此重传必须限速、分批、可确认。常见实现是滑动窗口:采集端按批(如每批 50 条)发送补传数据,服务端逐批返回 ACK,采集端收到 ACK 后才推进窗口并删除已确认的缓存记录;超时未收到 ACK 则重发该批。窗口大小和批大小需按链路带宽设定,例如上行有效带宽约 20 kbps 时,单批 50 条 × 64 字节约 3.2 KB,加上协议开销约 4 KB,单批传输约 1.6 秒,窗口取 2~3 批即可在吞吐与拥塞之间取得平衡。

同时应设置优先级:实时数据优先发送,补传数据在实时流空闲的间隙插入,或对补传流做令牌桶限速(如限制在链路带宽的 50%)。这样既保证当前工况可监可控,又让历史空洞逐步补齐。

去重与顺序保证:让补传数据“接得上、不重复”

由于 ACK 可能在回程丢失,采集端会重发同一批数据,服务端必然收到重复记录。去重的正确做法不是“收到就丢重复”,而是幂等写入:以 (device_id, seq) 作为唯一键,重复到达时执行覆盖写或忽略,保证同一 seq 在库中只有一条。这样即使重传多次,结果仍然一致,把传输层的“至少一次”语义收敛为业务层的“恰好一次”。

顺序保证则要区分两个层面。传输层按 seq 递增补传,保证同一设备的数据不乱序;存储层则不能假设到达顺序等于采集顺序——补传数据可能晚于实时数据到达。因此入库时应按 ts 排序或使用支持按时间窗口合并的存储策略,并在查询侧以 ts 为主序、seq 为次序。需要特别说明的是,断网期间设备无法对时,ts 可能存在漂移,所以 seq 才是判断“是否连续、是否缺失”的可靠依据;ts 只用于展示排序。若发现某设备 seq 出现跳变且缓存中无对应记录,应判定为“已超出可补传范围”,在数据质量标记中注明缺口,而不是静默拼接。

排障提示:判断断点续传是否真正生效,不要只看“数据条数对上了”。应同时检查三项——服务端该设备最大 seq 是否连续、缓存水位是否回落到低位、last_ack_seq 是否随 ACK 正常推进。三者一致,才说明缓存、断点、确认、去重整条链路都在工作。

把四个环节串起来看,断点续传的可靠性并不取决于某一个模块做得多强,而取决于它们之间的契约是否清晰:缓存定义“能补多久”,断点定义“从哪补”,确认定义“补到哪算数”,去重与排序定义“补进来怎么用”。在矿山这种链路频繁中断、设备可能掉电重启、带宽又十分有限的环境里,把这四个契约写进设备固件与服务端接口规范,比事后在平台侧做数据修补要可靠得多。