HelloWorld 断点续传指南

断点续传其实很简单:把大文件切成小片,给每个传输会话一个唯一 ID,记录每片的偏移与校验,客户端按序或并行上传,服务端保存状态并在最后把片段原子合并。关键在于幂等、校验与状态持久化,配合合理的重试与回退策略,才能既可靠又高效。

HelloWorld 断点续传指南

先说清楚:断点续传是什么,为什么要做

断点续传(resumable upload/download)指的是在网络中断、客户端崩溃或其他故障后,继续未完成的文件传输,而不需要从头再传一次。想象你在传一个几个 GB 的视频,突然断网——要是得重传一半,那就糟心了。断点续传把文件拆成小片,记录已到位的位置,恢复时只传剩下的片段。

常见场景

  • 移动端网络波动:4G/5G 切换、热点断开。
  • 桌面客户端上传大文件到云端。
  • 分布式备份或同步,需要容忍部分失败并可恢复。
  • 对接第三方对象存储(如 S3),需要签名 URL 和分片策略。

核心概念一览(用最简单的语言解释)

  • 分片(chunk/part):把大文件分成若干块,每块独立上传或校验。
  • 会话 ID(uploadId/sessionId):唯一标识一次上传流程,便于断点恢复。
  • 偏移量(offset):指明当前片段在整个文件中的起始位置。
  • 校验(checksum/ETag):每片或整文件的完整性校验,比如 MD5、SHA256。
  • 幂等性:重复上传同一片不会改变结果或造成错误。
  • 原子合并:所有片都到齐后,服务器以原子方式把片段合并成最终文件。

实现要点(分层来看:客户端与服务端)

客户端设计要点

  • 分片策略:大小选择一般在 256KB 到 10MB 之间,依据网络情况与并发能力。分片太小会产生大量请求,太大则恢复代价高。
  • 并发上行:支持 N 个并行分片上传(典型 3-6),既利用带宽又避免过载。
  • 状态持久化:把已上传的分片索引、会话 ID、服务器返回的 ETag 等持久化到本地(文件、数据库或 Key-Value 存储),重启后继续使用。
  • 重试与退避:短暂故障采用指数退避重试,区分可重试和不可重试的 HTTP 状态码。
  • 整合校验:每片上传后保存服务器校验(如 ETag),最后合并时再校验整文件哈希。

服务端设计要点

  • 会话管理:为每次上传创建 uploadId,记录包含文件元信息、期望大小、每片状态(已接收、已合并)等。
  • 幂等接口:上传同一分片多次返回相同结果(或明确告诉客户端已存在),避免重复写入。
  • 临时存储:未合并前将分片保存在单独路径或对象存储的临时命名空间,定期清理过期会话。
  • 并发与锁:合并步骤需要锁或事务,保证不会有两个进程同时合并同一文件。
  • 安全与权限:检查上传权限,短期签名 URL 或 Token,避免未授权上传或覆盖。

常见协议与实现方式

实现断点续传的方式有很多,从简单的 HTTP Range 到专用协议。下面列出几种常用方案和优劣。

  • HTTP Range(下载):客户端发带 Range 的 GET 请求,服务器返回对应字节范围,适合断点下载。
  • 分片上传 + 自定义 API(上传):客户端发起 CreateUpload(获取 uploadId),之后按 part 上传,最后发 CompleteUpload。
  • S3 Multipart Upload:成熟方案,支持分片上传、返回每片 ETag,然后 CompleteMultipartUpload。
  • TUS 协议:开源的断点续传协议,设计规范、支持扩展,生态良好。
  • 浏览器端库(resumable.js 等):简化实现,处理切片、重试、并发等细节。

一步步实现(一个实战流程)

下面用“发视频到云端”当例子,说明一步步要做的事。

第 1 步:开始上传(客户端请求创建会话)

  • 客户端请求:POST /uploads,带文件名、总大小、mime type、可选分片大小。
  • 服务端响应:{uploadId, expiresAt, suggestedPartSize}
  • 客户端保存 uploadId 和分片参数到本地持久化存储。

第 2 步:切片并上传

  • 按建议大小切片,给每片编号 partNumber 和 offset。
  • 为每片生成校验(如 SHA256)——可在客户端计算,也可由服务端验证。
  • 并行上传:PUT /uploads/{uploadId}/parts/{partNumber},附带内容、偏移、校验头。
  • 服务端保存分片并返回标识(ETag 或 partETag)。
  • 客户端记录每片的已完成状态和服务器返回的 ETag。

第 3 步:校验与合并

  • 当所有分片上传完成,客户端调用 POST /uploads/{uploadId}/complete,附带分片清单与每片 ETag。
  • 服务端验证每片 ETag、总长度,再以原子方式合并(或者在对象存储中调用多段合并)。
  • 合并成功后,服务端返回最终文件的 URL、ETag 和元信息。

第 4 步:失败恢复

  • 客户端重启或断网后,可调用 GET /uploads/{uploadId}/status 获取已接收的分片列表。
  • 只重传缺失或校验失败的分片。
  • 如果会话过期,可重新创建 uploadId 并尽量复用已上传的临时分片(如果服务端支持)。

接口示例(API 设计建议)

Endpoint Method 用途
/uploads POST 创建上传会话,返回 uploadId、建议分片大小、过期时间
/uploads/{uploadId}/parts/{partNumber} PUT 上传单个分片,需带偏移与校验头
/uploads/{uploadId}/status GET 查询已接收分片、状态与元信息
/uploads/{uploadId}/complete POST 提交合并请求,提供分片清单
/uploads/{uploadId}/abort DELETE 中止并清理会话与临时分片

设计细节与陷阱(不要踩的雷)

  • 不要只依赖客户端状态:客户端可能丢失本地记录,服务端应支持查询已上传分片。
  • 注意幂等性:PUT 同一 part 多次应该是可重试的,返回相同 ETag 或 200/204。
  • 锁与并发合并:合并时加分布式锁或用后端对象存储的原子合并 API。
  • 分片大小需要权衡:过小请求过多,过大恢复成本高;考虑网络 RTT 和带宽。
  • 过期与清理策略:临时分片需要定期清理,避免占满存储。
  • 校验粒度:推荐每片校验 + 最终整文件哈希,防止部分错写未发现。

性能与成本优化建议

  • 使用 CDN 或就近上传加速入口,减少 RTT。
  • 对常见小文件绕过分片流程,直接上传以降低开销。
  • 在对象存储支持下使用服务器端合并(如 S3 的 multipart complete),避免下载再拼接。
  • 为大文件提供按需压缩与分辨率选项,减少传输体积(视业务场景)。

安全性与权限控制

  • 上传请求应携带签名或短期 Token,防止滥用 uploadId。
  • 对临时分片路径使用权限隔离,避免未经授权的读取或覆盖。
  • 记录审计日志:谁、何时、哪些分片被上传或合并。

测试与校验清单(确保可用性)

  • 在断网、客户端重启、并行上传冲突等场景下做压力测试。
  • 验证重复上传同一分片的行为是否幂等,是否返回一致 ETag。
  • 测试会话过期后的行为:是否能安全清理或恢复。
  • 校验最终合并后的完整性哈希与期望值一致。

常见问题快速排查表

现象 可能原因 排查要点
断点之后无法继续上传 uploadId 失效或本地丢失会话 检查会话有效期,调用 status 接口确认已上传的分片
合并失败或文件损坏 分片缺失或 ETag 不匹配 对比分片清单与服务端记录,检查校验值
服务器负载高 过多并发分片写入或保留大量临时分片 限制并发上行、定期清理超期会话
重复写入导致成本上涨 幂等性处理不当,导致多次实际写入 实现幂等接口,返回已存在提示而不重复保存

现实中可借鉴的开源与服务

  • TUS 协议与 tusd 服务:标准化的上传协议,适合构建兼容客户端和服务器的生态。
  • S3 Multipart Upload:云厂商成熟实现,兼容性好,适合生产环境。
  • resumable.js:浏览器端分片上传库,处理切片、重试与并发。

好啦,按上面步骤去做,会让你的断点续传既稳又能应对各种网络“突发情况”。我自己实现过类似方案:把分片大小调到 4MB、并发 4 条、每片保存 SHA256、会话保存 7 天,效果挺稳的——只是需要在细节上多做容错与清理策略,别让临时数据变成长期负担。