os.readfile会将整个大文件一次性加载到内存,导致oom;必须用os.open配合readat流式读取。multipart.newreader逐part解析,不缓存全文,比parsemultipartform更省内存。

为什么不能用 os.ReadFile 读大文件再上传
它会把整个文件塞进内存,1GB 文件就占 1GB 堆空间,GC 压力陡增,极易触发 OOM。这不是慢的问题,是根本不可行。
- 必须改用
os.Open获取*os.File,再配合流式读取逻辑 - 若用
bufio.NewReader包裹后调ReadAt,会破坏随机读 offset ——ReadAt要求底层io.ReaderAt实现,而bufio.Reader不满足 - 真正安全的分片读法:直接对
*os.File调ReadAt(buf, offset),并始终检查n == len(buf)或err == io.EOF
multipart.NewReader 为什么比 ParseMultipartForm 更适合大文件
ParseMultipartForm(32 默认缓存最多 32MB 在内存,但只要 multipart body 里有大文件,Go 仍会先把它整个读进临时磁盘文件(<code>/tmp),且无法控制单个 part 的大小上限。更糟的是,它会阻塞直到解析完全部字段和文件,延迟不可控。
- 绕过
ParseMultipartForm,直接用multipart.NewReader(r.Body, boundary) - 对每个
part,立刻调part.Open()得到io.Reader,再用io.CopyN(dstFile, partReader, expectedSize)限长写入 - 这样能边收边写,不缓存整块数据,也不依赖
r.FormValue—— 普通字段需前端改用 JSON 体或手动解析
并发上传时 goroutine 数量设多少才不拖慢磁盘
上传本质是 I/O 密集型,不是 CPU 密集型。盲目开上百 goroutine,只会让内核在磁盘队列上排队,尤其在 HDD 或低配云盘上吞吐反降。
- SSD 上建议硬限流到 8–16 个并发;机械盘 ≤4
- 用带缓冲 channel 当信号量:
sem := make(chan struct{}, 12),每次上传前sem ,完成后 <code> - 避免复用同一
os.File句柄被多个 goroutine 同时WriteAt—— offset 错乱风险极高,应按序合并而非并发写入最终文件
分片大小设 1MB 还是 5MB?关键看网络与校验需求
太小(如 128KB)导致 syscall 过多、TCP 包碎片化;太大(如 50MB)则单次失败重传成本高,且服务端 buffer 分配压力大。
- 推荐 1–5MB 区间,实测 3MB 在多数千兆带宽+SSD 场景下吞吐最优
- 若需边读边算 SHA256,则分片越小,校验粒度越细,但要注意
hash.Hash实例复用,别每片 new 一个 - 客户端必须携带
X-Chunk-Index和X-Total-Chunks,服务端收到后立即校验Content-Length是否匹配预期大小,防前端 bug 或恶意截断
分片合并那步最容易被忽略:必须先确认所有 0..N-1 分片文件都存在,缺一片就直接返回 400 Bad Request;合并时用 io.MultiReader 按序拼接,而不是靠 WriteAt 手动 seek —— offset 计算稍错一点,整个文件就损坏。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











