真正的“秒级”合并靠规避阻塞与精确控制io模式:需确保分片写入完成(调用file.sync)、状态校验、waitgroup通知、io.multireader流式拼接、新文件写入+原子重命名、细粒度锁及幂等清理。

大文件分片上传后,合并不能卡在主线程里等几秒甚至几十秒——os.Rename 或 io.Copy 本身不慢,但并发写入、路径竞争、临时文件残留会让合并变成不可控的延迟点。真正的“秒级”靠的是规避阻塞 + 精确控制 IO 模式。
合并前必须确认分片已全部写入完成
常见错误是收到最后一个分片请求就立刻触发合并,但此时前序分片可能还在内核缓冲区或磁盘写队列中。Gin handler 返回 HTTP 200 并不代表 os.WriteFile 已刷盘。
- 每个分片上传接口必须调用
file.Sync()(或用os.O_SYNC打开文件),否则os.Rename可能移动未写完的文件 - 服务端需维护分片状态表(如
map[fileMd5][]int),仅当len(chunkList) == ChunkTotal且所有分片文件存在且大小匹配时才允许合并 - 避免轮询检查:用
sync.WaitGroup或 channel 在分片写入完成后通知合并协程,而不是反复os.Stat
用 io.MultiReader + os.Create 直接拼接,不落地中间文件
不要把所有分片先读进内存再 Write,也不要逐个 io.Copy 追加——前者 OOM,后者磁盘寻道放大延迟。真正快的方式是让内核流式组装。
- 按分片序号排序,生成
[]io.Reader切片,每个元素是os.Open后的文件句柄 - 用
io.MultiReader包装,它返回一个顺序读取的io.Reader,无内存拷贝 - 目标文件用
os.O_CREATE | os.O_WRONLY | os.O_TRUNC打开,直接io.Copy(dst, multiReader) - 最后关闭所有分片句柄(别漏!否则 Windows 下重命名失败)
os.Rename 不等于原子合并,慎用
很多人以为 os.Rename 是“秒级”的,但它只保证路径变更原子性,不保证内容完整。若分片目录被误删或挂载点异常,Rename 会失败且无回滚。
- 生产环境推荐用
os.Create+io.Copy写新文件,成功后再os.RemoveAll临时目录 - 若坚持用
Rename,确保源路径和目标路径在同一文件系统(syscall.Errno 18表示跨设备,会退化为 copy+delete) - 合并过程加
filepath.Base(fileMd5)级别互斥锁(sync.Mutex或 redis lock),防止同一文件被多次触发合并
合并失败时的状态清理必须幂等
网络中断、磁盘满、权限错误都可能导致合并中途退出,留下半成品文件或残留分片。手动删或定时任务清理都不够可靠。
- 每个分片上传时记录
mtime和size,合并失败后扫描临时目录,自动清理超时(如 >24h)且未被任何ExaFile记录引用的分片 - 最终文件写入前先写到
.tmp后缀,成功后再os.Rename去后缀——这样即使崩溃,也不会污染目标路径 - 不要依赖
defer清理:合并逻辑在 goroutine 中执行,defer在 handler 返回时就结束了
真正卡住“秒级”的从来不是算法,而是文件系统行为、锁粒度和错误恢复路径。分片合并不是终点,而是状态机里一个可重入、可取消、可观测的环节。











