分片上传并发冲突解决:多人同时传同名文件?分布式锁保障数据完整!
公司的网盘系统出了个诡异的 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
公众号:服务端技术精选
评论