直接用http.post上传大文件会失败,因客户端将整个文件读入内存导致oom,服务端易超时或拒绝;网络中断后无法续传,必须采用客户端分片、服务端拼接与校验位记录方案。

为什么直接用 http.Post 上传大文件会失败
因为默认 HTTP 客户端没有分片逻辑,整个文件读入内存或一次性发包,超时、OOM、网络中断后全部重传。断点续传的核心不是“传得快”,而是“传得稳”——得知道哪一片传过了、哪一片卡住了、校验是否一致。
- 别用
os.ReadFile加载整个文件到内存,1GB 文件直接触发 GC 压力甚至 panic -
http.DefaultClient的Timeout默认是 30 秒,上传 2GB 文件必然超时,且超时后无法恢复已传部分 - 服务端没做分片接收和合并,客户端再怎么分也没用;必须前后端约定同一套分片标识(如
filename+chunkIndex+totalChunks)
用 io.Pipe + multipart.Writer 构建流式分片上传
关键不是“把文件切开存成多个临时文件”,而是边读边传、不落地、不缓存整块。Pipe 让读取和上传并发进行,避免内存堆积。
- 每个分片用独立的
http.Request,Content-Type设为multipart/form-data,用multipart.Writer写入file字段和元数据字段(chunkIndex、totalChunks、md5等) - 打开文件用
os.Open,然后io.Seek定位到分片起始偏移,用io.CopyN或io.LimitReader控制本次读取长度 - 务必设置
req.Header.Set("X-Chunk-Index", strconv.Itoa(i))和req.Header.Set("X-Total-Chunks", strconv.Itoa(total)),方便服务端做幂等判断
// 示例:构造单个分片请求
f, _ := os.Open("big.zip")
defer f.Close()
f.Seek(int64(i)*chunkSize, 0)
pr, pw := io.Pipe()
mw := multipart.NewWriter(pw)
<p>go func() {
defer pw.Close()
fw, _ := mw.CreateFormFile("file", "big.zip")
io.CopyN(fw, f, chunkSize)
mw.WriteField("chunkIndex", strconv.Itoa(i))
mw.WriteField("totalChunks", strconv.Itoa(total))
}()</p><p>req, _ := http.NewRequest("POST", "<a href="https://www.php.cn/link/f142c6067e6345134c6728f299cf4c1e">https://www.php.cn/link/f142c6067e6345134c6728f299cf4c1e</a>", pr)
req.Header.Set("Content-Type", mw.FormDataContentType())
</p>
如何判断某分片是否已上传成功并跳过
不能靠客户端本地记录,必须和服务端对账。最可靠的方式是上传前先发一个 HEAD 或 GET 请求,带 X-Chunk-Index 和文件唯一标识(如 md5(filename)),由服务端返回该分片状态。
- 服务端应返回
204 No Content表示已存在,404表示未上传,200带校验值表示需比对 - 客户端拿到响应后,用本地计算的分片
sha256和服务端返回的 hash 比对,不一致才重传——这点常被忽略,导致损坏文件被“续传”过去 - 不要用时间戳或本地 JSON 记录上传状态,设备重装、路径变更、多设备同步都会让记录失效
并发控制与错误重试的实际写法
并发数不是越大越好。runtime.NumCPU() 是参考下限,但真实瓶颈常在服务端连接数或磁盘 IO。建议初始设为 3–5,并暴露可调参数。
- 每个分片上传单独 select + context.WithTimeout,超时后 cancel 当前请求,不影响其他分片
- 重试只针对网络错误(
url.Error)和 5xx 响应,4xx(如 400、409)说明参数或服务端逻辑有问题,重试无意义 - 重试间隔用指数退避:
time.Second * (1 ,避免雪崩式重连
最难处理的其实是“上传完成但服务端合并失败”——这时客户端看到所有分片返回 200,却收不到最终文件 URL。必须额外提供一个 /upload/merge?uploadId=xxx 轮询接口,或让服务端通过 webhook 主动通知。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











