分片上传并发冲突解决:多人同时传同名文件?分布式锁保障数据完整!

公司的网盘系统出了个诡异的 bug。用户 A 上传了一个 500MB 的视频文件,分成了 100 个分片。上传到第 80 片的时候,用户 B 也上传了一个同名的视频文件——可能是一个修订版。两个上传请求同时往同一个文件名下写分片,最后合并出来的文件里混了一半 A 的内容和一半 B 的内容。文件打不开,用户投诉。

分片上传的并发冲突比单文件上传难处理得多。单文件上传一次 HTTP 请求就完成了,有冲突也容易看出来。分片上传是几十上百个 HTTP 请求,冲突可能发生在任何时候——第一个分片被 B 覆盖,最后一个分片还是 A 的。结果就是一个"拼接怪物"。

今天聊聊怎么用分布式锁把分片上传的整个过程保护起来,确保同一时间只有一个上传任务在操作同一个文件。


冲突是怎么发生的

分片上传的流程通常是这样:

1. 前端发起分片上传 → 后端返回 uploadId
2. 前端逐个上传分片 → 每片带 uploadId + 分片号
3. 全部分片上传完 → 发起合并请求

两个人的冲突:

时间线:    用户 A                   用户 B
  T1     创建 uploadId=A1
  T2     上传分片 0~50
  T3                              创建 uploadId=B1
  T4                              上传分片 0~30
  T5     上传分片 51~80            (A 的分片中混入了 B 的序号)
  T6                              上传分片 31~100
  T7                              发起合并 → 拼出来文件一半是 A 一半是 B

问题的根源是:两个 uploadId 操作的是同一个目标文件名,但没有任何机制阻止它们并发执行。


方案:分布式锁,文件名级别的互斥

靠 uploadId 来区分上传任务不够——两个不同的人各自创建了不同的 uploadId,但它们的目标文件是同一个。需要在文件级别做互斥。

获取锁(文件名为 key):

  用户 A 上传 "/videos/demo.mp4"
    → 尝试获取锁 "lock:upload:videos/demo.mp4"
    → 获取成功 → 创建 uploadId,开始上传

  用户 B 上传 "/videos/demo.mp4"
    → 尝试获取锁 "lock:upload:videos/demo.mp4"
    → 获取失败(A 持有锁) → 返回"文件正在上传中,请稍后重试"

Redis 分布式锁实现:

public String initUpload(String fileName, String userId) {
    String lockKey = "lock:upload:" + fileName;
    String lockValue = UUID.randomUUID().toString();

    // 尝试获取锁,超时时间 600 秒(覆盖大文件上传)
    boolean locked = redis.set(lockKey, lockValue, "NX", "EX", 600);

    if (!locked) {
        throw new BizException("文件正在上传中,请稍后重试");
    }

    // 拿到锁,生成 uploadId
    String uploadId = generateUploadId();
    redis.set("upload:" + uploadId + ":lock", lockValue);
    redis.set("upload:" + uploadId + ":userId", userId);
    redis.set("upload:" + uploadId + ":fileName", fileName);

    return uploadId;
}

锁的超时时间要足够长——覆盖整个大文件上传的周期。如果文件特别大(比如 10GB),可以分批加锁续期:每上传一部分分片就续期一次锁的过期时间。


锁的释放:不能靠超时

锁的超时是兜底,不能当正常释放来用。正常流程应该是:

上传完成(全部分片收到 + 合并成功)→ 主动释放锁
上传取消(用户主动取消)→ 主动释放锁
上传异常(网络断开、超时)→ 等待锁过期自动释放

但有个细节:只有持有锁的人才能释放锁。 这是一个经典坑点——如果 A 的锁过期了但 A 还在上传,B 拿到了锁开始上传,然后 A 的合并请求误删了 B 的锁。

解决办法:释放锁时比对 lockValue,只有 value 一致才允许释放:

public void releaseLock(String fileName, String lockValue) {
    String lockKey = "lock:upload:" + fileName;
    String current = redis.get(lockKey);
    if (lockValue.equals(current)) {
        redis.del(lockKey);
    }
}

Redis 的 Lua 脚本可以把读+校验+删做成原子操作,避免中间被插队。


冲突时的应对策略

不是所有冲突都该粗暴拒绝。根据业务场景可以选不同策略:

严格模式(网盘、文档库):同名文件不允许并行上传。后上传的人收到"文件正在处理中"的提示,排队或稍后重试。

版本模式(协同编辑、制品库):允许并行上传,但会给每个上传自动生成带版本号的文件名。A 上传的叫 demo_v1.mp4,B 的叫 demo_v2.mp4。没有冲突,各自上传各自合并。

覆盖模式(日志归档、备份):后上传的人覆盖前面的人。这时上传锁的作用是保证"覆盖"这个动作是原子的——先等 A 传完合并好,B 再从头开始传。


分片上传全流程保护

把锁串到整个分片上传的生命周期里:

初始化:
  → 获取文件级分布式锁
  → 创建 uploadId
  → 返回 uploadId 给前端

上传分片:
  → 校验 uploadId + 锁持有者
  → 写入分片
  → 续期锁(每 10 个分片续一次)

合并:
  → 校验 uploadId + 锁持有者
  → 校验所有分片完整性
  → 合并文件
  → 释放锁

每个分片上传的时候都校验一把锁——如果锁过期了或被别人抢走了,当场拒绝分片写入。这样即使锁超时,也不会出现"A 的分片和 B 的分片混在一起"的脏数据。


总结

分片上传的并发冲突,核心原因是有两个无状态的 uploadId 在操作同一个有状态的资源(文件)。

文件级分布式锁就是把这个有状态的资源保护起来。同一时间,同一个文件只有一个上传任务在工作。

几个关键操作:

  • 初始化时抢锁——SET NX EX,锁的 key 是文件路径
  • 分片时续期——大文件分几百片,每隔几片续一次锁
  • 释放时验 ownership——不是自己拿的锁不能释放
  • 冲突时选策略——严格/版本/覆盖,按业务场景选

锁的开销很小——一次 Redis 操作就几毫秒。但它的存在让分片上传从"偶尔文件损坏"变成了"数据完整性有数学保障"。


有用的话转给还在手动排查"视频打不开"问题的同事。


标题:分片上传并发冲突解决:多人同时传同名文件?分布式锁保障数据完整!
作者:jiangyi
地址:http://jiangyi.space/articles/2026/06/19/1781786571404.html
公众号:服务端技术精选
    评论
    0 评论
avatar

取消