4. 断点续传基本原理

在车云消息可靠传输体系中,断点续传解决的是“连接中断后如何不重不漏地继续传输”这一核心问题。车端与云端之间的链路会经历隧道、弱网、基站切换、休眠唤醒等复杂场景,TCP 长连接随时可能断开。如果没有断点续传机制,每次重连都从零开始,既浪费流量又造成消息重复或丢失。本节从断点定义、续传触发条件、状态保存与恢复、幂等性设计四个维度,讲清断点续传的底层原理。

一、断点的定义:不是“断线位置”,而是“确认边界”

很多初学者把断点理解为“最后一条发送失败的消息位置”,这是不准确的。在可靠传输中,断点的本质是已确认消息的连续边界。也就是说,断点之前的所有消息,云端都已经持久化并返回了确认;断点之后的消息,要么未发送,要么发送了但未收到确认,属于“不确定状态”。

因此断点通常用一个单调递增的序列号(seq)或偏移量(offset)来表示。车端维护 sendSeq,云端维护 ackSeq,断点即 ackSeq 所指向的位置。只有被确认的连续序列才能推进断点,出现空洞时必须等待补齐,否则断点会“跳跃”,导致中间消息永久丢失。

断点 = 已确认消息的最大连续序列号。它代表“从这里之前的都安全了”,而不是“最后一条消息发到哪了”。

二、续传触发条件:什么时候该续传

续传不是随时都触发,需要明确的判断条件,否则会造成无谓的重传和状态混乱。常见触发条件包括:

  • 连接重建成功:TCP/MQTT 重连并完成鉴权后,车端主动发起续传协商。
  • 收到云端断点查询响应:云端返回 ackSeq,车端据此判断是否需要补发。
  • 本地存在未确认消息:发送队列中仍有 seq > ackSeq 的消息。
  • 会话未过期:断点状态仍在有效期内,未因超时被清理。
  • 网络质量满足阈值:RSSI、丢包率恢复到可传输水平,避免刚续传又断。

反之,如果云端返回的 ackSeq 已经等于车端 sendSeq,说明没有遗漏,无需续传;如果会话已过期,则应放弃旧断点,开启新会话并做全量或快照同步。

三、状态保存与恢复:断点存在哪里

断点状态必须双端持久化,且要能经受进程重启、掉电、休眠。车端通常把 sendSeq、未确认消息队列、会话 ID 写入本地持久化存储(如 SQLite、文件或 NVRAM);云端把 ackSeq、消息去重表、会话元数据写入数据库或分布式缓存。

恢复流程一般是:重连 → 云端下发 ackSeq → 车端对比本地 sendSeq → 从 ackSeq+1 开始补发 → 云端逐条确认并推进 ackSeq。这里的关键是确认要单调、连续,不能因为乱序到达就跳跃确认。

断点续传:状态保存与恢复流程 车端持久化状态 sendSeq = 105 未确认队列 106~110 sessionId = S-778 云端持久化状态 ackSeq = 105 去重表 msgId 集合 sessionId = S-778 重连后查询断点 ackSeq 返回 ackSeq=105 恢复动作:从 ackSeq+1 = 106 开始补发,逐条等待确认,ackSeq 连续推进 106 → 107 → 108 → 109 → 110,全部确认后 sendSeq 与 ackSeq 对齐 幂等键:msgId 重复到达只落库一次 空洞检测 缺号则等待,不跳跃确认 会话过期 放弃旧断点,重建会话

四、幂等性设计:重复不可避免,重复必须无害

断点续传中,消息重复几乎无法避免:车端发出消息后连接断开,未收到确认,重连后会重发;云端可能已经处理过这条消息。因此必须通过幂等性设计,让“重复到达”不产生副作用。

常见做法是为每条消息分配全局唯一的 msgId,云端维护去重表,处理前先查 msgId 是否已存在,存在则直接返回成功确认,不再执行业务逻辑。对于有状态业务,还可以使用版本号或状态机约束,例如“只有当前状态允许时才执行迁移”。

// 云端幂等处理伪代码
if (dedupTable.contains(msg.msgId)) {
    return ACK(msg.seq);          // 已处理,直接确认
}
dedupTable.add(msg.msgId);
businessHandler.process(msg);
ackSeq = max(ackSeq, msg.seq);    // 连续确认
return ACK(msg.seq);

五、典型场景与常见误区

以车机上报行车数据为例:车辆进入隧道前已发送到 seq=105,隧道内断网,106~110 积压在本地队列。出隧道重连后,云端返回 ackSeq=105,车端补发 106~110,云端逐条确认并推进到 110,断点续传完成。整个过程用户无感知,数据不丢不重。

误区后果正确做法
断点只存内存重启后断点丢失,全量重传持久化到本地存储
乱序到达就跳跃确认中间消息永久丢失只推进连续确认边界
没有 msgId 去重重复入库、重复计费云端幂等表去重
会话永不过期断点状态无限膨胀设置 TTL 并定期清理
重连即全量重传流量浪费、云端压力大先协商 ackSeq 再补发
工程建议:断点状态与会话 ID 绑定,会话过期后旧断点作废;确认必须连续单调;幂等键建议使用“设备ID+会话ID+序列号”组合,避免跨会话冲突。
特别注意:如果云端先落库后确认,而车端在确认丢失后重发,必须保证落库操作本身幂等,否则会出现“确认成功但数据重复”的隐蔽问题。

总结来看,断点续传的四个支柱缺一不可:断点定义保证“知道从哪继续”,触发条件保证“该续时才续”,状态保存与恢复保证“重启后还能续”,幂等性设计保证“重复了也不出错”。四者配合,才能在车云弱网、断连、休眠等真实工况下实现消息的不丢不重。