文章 569
评论 5
浏览 218141
大文件分片上传断点续传:网络中断进度丢失?Redis 记录分片状态+秒级续传

大文件分片上传断点续传:网络中断进度丢失?Redis 记录分片状态+秒级续传

公司的用户上传 2GB 的视频文件到网盘,传了 80% 的时候网络断了。页面刷新后,上传列表里空空如也——前端状态丢失,后端也没记录哪些分片已经上传成功。用户只能从头再来。2GB 的文件,从头再传一遍,用户的耐心也从头归零。

分片上传天然支持断点续传——前提是你知道哪些分片已经上传成功了。不知道的话,就只能全量重传。今天聊聊怎么用 Redis 记录每个分片的上传状态,让网络断了之后从断点秒级续传,而不是从头开始。


分片上传的断点问题

分片上传的流程很简单:前端把大文件切成 N 片,逐片上传。后端每收到一片就存起来。

文件 2GB → 切 200 片 × 10MB → 逐片上传

如果第 150 片上传失败(网络断了、服务器卡了),最好的情况是从第 150 片继续传。但前端不知道自己传了哪些片——因为前端的进度存在内存里,页面一刷新就没了。

后端需要提供一个能力:告诉前端哪些分片已经上传成功了。


Redis 记录分片状态

给每个上传任务一个 uploadId(UUID),用 Redis 的 Set 记录已上传的分片编号:

// 记录分片已上传
redis.sadd("upload:" + uploadId + ":parts", String.valueOf(partNo));

// 查询已上传的分片列表
Set<String> completed = redis.smembers("upload:" + uploadId + ":parts");

// 全部上传完 → 合并
long totalParts = redis.get("upload:" + uploadId + ":total");
if (completed.size() == totalParts) {
    mergeFile(uploadId);
}

Redis Set 的 sadd 天然幂等——同一片重复上传不会报错。smembers 返回所有已上传分片,前端一查就知道"0~149 已经传完了,从第 150 片开始"。


断点续传的完整流程

前端:启动上传
  ├─ 计算文件 MD5 + 总分片数
  ├─ POST /api/upload/init → 返回 uploadId
  │   后端:生成 uploadId,记录总分片数到 Redis
  │
  ├─ 逐片上传(POST /api/upload/part)
  │   后端:sadd upload:{id}:parts {partNo},写文件
  │
  ├─ 网络中断 → 页面刷新
  │
  ├─ 重新启动上传(同一文件同一 MD5)
  │   前端:POST /api/upload/resume?md5=xxx
  │   后端:根据 MD5 找到之前的 uploadId
  │   返回:{ uploadId, completed: [0,1,2,...149] }
  │
  ├─ 从第 150 片继续传
  │
  └─ 全部完成 → POST /api/upload/merge
       后端:校验完整性 → 合并分片 → 清理 Redis

用文件 MD5 做断点续传的 key。同一个文件,即使两次上传间隔了几天,只要 MD5 一样,就能找到之前的进度继续传。


Redis 过期时间:别让 Key 永久占用

分片状态不用永久保留。上传完成后立即删,如果上传中断了,Key 设一个合理过期时间(比如 24 小时),过期自动清理:

// 初始化时设过期
redis.set("upload:" + uploadId + ":total", String.valueOf(totalParts), 24 * 3600);

// 每上传一片,续期一次
redis.expire("upload:" + uploadId + ":parts", 24 * 3600);

24 小时是合理的——用户如果 24 小时都没重新上传,大概率放弃了,清理状态释放 Redis 内存。


前端怎么判定"同一个文件"

不是靠文件名,是靠文件 MD5。文件名改了、路径改了、甚至文件在本地被移动了——只要内容没变,MD5 一样,就能续传。

// 前端计算文件 MD5(Web Crypto API)
async function fileMd5(file) {
    const buffer = await file.arrayBuffer();
    const hash = await crypto.subtle.digest('SHA-256', buffer);
    return Array.from(new Uint8Array(hash))
        .map(b => b.toString(16).padStart(2, '0')).join('');
}

计算 MD5 本身也要时间——2GB 的文件,浏览器里算 MD5 要好几秒。可以异步在 Worker 线程里算,不阻塞 UI。


完整的后端实现

@PostMapping("/upload/init")
public UploadInfo init(@RequestParam String md5, @RequestParam int totalParts) {
    // 先查是否存在相同 MD5 的上传任务
    String existingId = redis.get("upload:md5:" + md5);
    if (existingId != null) {
        Set<String> parts = redis.smembers("upload:" + existingId + ":parts");
        return new UploadInfo(existingId, parts); // 返回已完成的分片
    }

    String uploadId = UUID.randomUUID().toString();
    redis.set("upload:md5:" + md5, uploadId, 24 * 3600);
    redis.set("upload:" + uploadId + ":total", String.valueOf(totalParts), 24 * 3600);
    return new UploadInfo(uploadId, Collections.emptySet());
}

@PostMapping("/upload/part")
public void uploadPart(@RequestParam String uploadId,
        @RequestParam int partNo, @RequestPart MultipartFile part) {
    savePartFile(uploadId, partNo, part);
    redis.sadd("upload:" + uploadId + ":parts", String.valueOf(partNo));
    redis.expire("upload:" + uploadId + ":parts", 24 * 3600);
}

总结

分片上传的断点续传,核心就是"记住哪些片已经传过了"。

  • Redis Set 记录分片——sadd 幂等、smembers 查询
  • 文件 MD5 做断点 key——同一文件多次上传自动续传
  • 24 小时过期自动清理——不上传了不占 Redis 内存

配完之后,2GB 文件传了 80% 断了,刷新页面秒级续传剩下的 20%,用户体验从"崩溃"到"无感"。



标题:大文件分片上传断点续传:网络中断进度丢失?Redis 记录分片状态+秒级续传
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/07/06/1783149371766.html
公众号:服务端技术精选

服务端开发博客:后端架构、高并发、性能优化与微服务实战教程

取消