直接用io.Copy往http.ResponseWriter或bytes.Buffer写大文件会导致OOM或卡死,因默认32KB缓冲在NFS等高延迟存储上吞吐暴跌;必须禁用r.ParseMultipartForm自动解析、用io.LimitReader流式截取分片、os.OpenFile配合Seek精准写入,并以io.MultiReader按chunkIndex升序合并。

直接用 io.Copy 往 http.ResponseWriter 或 bytes.Buffer 里写大文件,90% 的服务会在 GB 级上传时 OOM 或卡死——这不是代码写得不够好,而是默认行为根本没为流式设计。
必须绕开 r.ParseMultipartForm 的自动解析陷阱
Go 的 http.Request 在首次访问 r.MultipartForm 或显式调用 r.ParseMultipartForm(哪怕参数是 0)前,r.Body 是原始字节流;一旦触发解析,整个 multipart body 就被消费掉,后续再读 r.Body 就返回 EOF 或空数据。
- 分片上传场景下,第一行必须写
r.ParseMultipartForm(0),传 0 表示禁用内存缓存、跳过自动解析 - 元信息(如
upload_id、chunk_index)从r.URL.Query().Get("upload_id")或r.FormValue("chunk_index")拿,别碰r.MultipartForm.Value - 二进制分片数据直接从
r.Body流读,用io.LimitReader(r.Body, chunkSize)截断,避免越界或读穿 - 并发写同一分片时,打开文件用
os.O_CREATE | os.O_WRONLY | os.O_EXCL,防止覆盖
写文件不能用 os.O_APPEND,否则断点续传必损坏
os.O_APPEND 会让所有 Write() 忽略之前 Seek() 的偏移,强制追加到末尾。结果就是:你本想写第 10MB,却在文件末尾新增了一段,中间全是 \x00 填充,文件直接损坏。
- 正确打开方式:
os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0644),不加O_TRUNC、不加O_APPEND - 打开后立刻
f.Seek(offset, io.SeekStart),并检查返回值是否等于offset - 合并分片时,用
io.MultiReader按chunkIndex升序构造完整流,而不是靠os.WriteAt手动拼接——分片大小不均时极易错位
io.Copy 缓冲区要按存储介质调优,32KB 在 NFS 上吞吐暴跌
标准库 io.Copy 默认用 32KB 缓冲,在本地 SSD 上尚可,但在 NFS、Ceph、云盘等高延迟存储上 syscall 过多,实测吞吐下降 3–5 倍。
立即学习“go语言免费学习笔记(深入)”;
- 对磁盘写入,建议用
io.CopyBuffer(w, r, make([]byte, 1024*1024))(1MB 缓冲) - 别用
sync.Pool复用这个缓冲——压测反而更慢,GC 开销抵消了收益 - 若上游不可控(如代理转发),给
Write加time.AfterFunc超时监控,主动断连防 hang - 绝对别用
bytes.Buffer.ReadFrom(r)或已弃用的ioutil.ReadAll,效果等同于os.ReadFile
gRPC 双向流传大文件,proto 定义错一个字就废
gRPC 不是“开了 stream 就自动流式”,proto 里 stream 关键字位置错了,生成的 Go 接口就完全不能用。
- 客户端流必须是
rpc Upload(stream Chunk) returns (UploadResult);写成rpc Upload(Chunk) returns (stream UploadResult)就变成服务端流,Recv()永远等不到数据 - 客户端每次
Send()都要构造新*pb.Chunk实例,别复用 proto message(字段会被覆盖) - 服务端接收不能用
for range stream.Recv(),得用带超时的stream.RecvMsg()+select控制 - 单次
data字段别超过 1MB,且禁止定义repeated bytes data——序列化器会试图 hold 整个切片,OOM 风险极高
真正难的不是“怎么把字节传过去”,而是让每一块数据都精准落盘、可校验、可中断恢复;所有看似细小的开关(比如 O_APPEND、ParseMultipartForm(0)、stream 关键字位置),漏掉任何一个,都会在 GB 级压力下暴露为不可恢复的数据损坏或服务雪崩。


















