正确做法是用 io.copy 追加已排序的分片文件:先按 chunkindex 数值排序分片文件名,再用 os.open 打开并 io.copy 到目标文件,自动校验长度;并发写分片需用 uploadid 隔离+os.o_excl 原子创建防覆盖;parsemultipartform 必须显式设置足够内存阈值。

为什么不能直接用 os.Write 合并分片
直接循环调用 os.Write 写入每个分片,极易因偏移计算错误或 n != len(buf) 导致文件中间缺字节——比如第 3 片写入时实际只写了 4999999 字节(缺 1 字节),后续所有数据都会错位。更危险的是,这种损坏无法在合并过程中被发现,直到最终校验失败或业务读取崩溃。
正确做法是用 io.Copy 追加已排序的分片文件:
- 先按
chunkIndex对分片文件名做数值排序(不是字符串排序,否则10会排在2前) - 用
os.Open打开每个分片,io.Copy到目标文件句柄,它自动处理读写长度匹配 - 每次
io.Copy返回实际字节数,可与分片声明长度比对,不一致立即中断
如何避免并发写分片导致数据丢失
多个 HTTP 请求同时上传同一上传会话的不同分片时,若共用一个临时目录且不做控制,会出现「后写覆盖前写」或「重复写入」。关键不是加锁,而是用原子性操作拒绝冲突:
- 每个上传会话生成唯一
uploadID(如 UUID),分片存于/tmp/upload_<code>uploadID/chunkIndex - 写入前调用
os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)—— 若文件已存在,返回os.ErrExist,此时应跳过或返回 200(幂等) - 绝不依赖文件名哈希做隔离:同名文件 + 同用户 = 冲突;也不用全局锁,会成为性能瓶颈
ParseMultipartForm 设置不当会导致分片上传失败
Go 的 multipart/form-data 解析器默认内存阈值仅 32MB,但这个值必须显式调用 r.ParseMultipartForm(32 设置,否则:<code>r.FormFile("file") 会直接报 http: no such file 错误,哪怕前端确实传了。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
阈值设太小(如 1 )会让每个 5MB 分片都落盘,IO 翻倍拖慢吞吐;设太大(如 <code>512 )又可能 OOM。实测建议:
- 分片大小 ≤ 5MB 时,设为
32 (32MB)足够 - 若分片 ≥ 10MB,阈值至少设为分片大小 × 2,留出解析开销余量
- 务必在
r.FormValue读元数据之前调用,顺序不能错
校验失败后必须立刻清理残留文件
最终文件哈希不匹配客户端提交的 X-Final-SHA256,说明传输过程有损坏或篡改。此时不能只返回错误,必须同步删除:
- 刚生成的最终文件(防止下游误用脏数据)
- 该
uploadID下所有分片(避免磁盘被无效文件占满) - 整个临时目录
/tmp/upload_<code>uploadID(用os.RemoveAll)
没删干净的分片,会在下次定时清理前持续占用磁盘——而清理逻辑依赖 os.Chtimes 记录最后写入时间,一旦某片写入失败没更新时间戳,就可能被误删或永久滞留。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










