直接用goroutine并发上传分片失败是因未限制并发数,导致文件描述符耗尽及服务端限流;应控制同一时刻http请求数,用带缓冲channel(如make(chan struct{}, 3))实现轻量可控的并发限制。

为什么直接用 goroutine 并发上传分片会失败
因为没做并发数限制,瞬间拉起几百个 http.Post,操作系统很快耗尽文件描述符,出现 dial tcp: lookup xxx: no such host 或 too many open files 错误;服务端也大概率触发限流或连接拒绝。
真正要控制的不是“启多少 goroutine”,而是“同一时刻最多发几个 HTTP 请求”。semaphore 或带缓冲的 channel 是最轻量、最可控的方式。
- 用
make(chan struct{}, N)控制并发上限(比如N = 3),每个分片上传前先sem ,结束后 <code> - 别用
sync.WaitGroup单独配合go启动——它不阻塞,无法限流 - 注意:HTTP client 的
Transport.MaxIdleConnsPerHost也要设为 ≥ 并发数,否则底层复用连接失败,反而加重开销
分片上传必须校验 MD5 还是 SHA256
MD5 在传输层已不安全,且容易碰撞;但如果你的服务端只认 MD5(比如对接旧版七牛、又拍云),那客户端就只能算 MD5。关键不是算法本身,而是前后端必须严格一致。
更现实的问题是:大文件不能一次性读进内存算哈希。得用 io.Copy 配合 hash.Hash 流式计算。
- 对每个分片单独计算:打开文件 →
io.Copy(hasher, io.LimitReader(file, size))→ 得到该分片的hex.EncodeToString(hasher.Sum(nil)) - 不要等所有分片上传完再算整体文件哈希——服务端通常要求每个分片带独立校验值
- 如果服务端支持秒传,你还得提前对整个文件算一次 SHA256(同样流式),用于判断是否已存在
如何安全地重试失败的分片上传
网络抖动导致某个分片 504 Gateway Timeout 或 context.DeadlineExceeded,不能简单 for-loop 重试——可能因服务端已接收部分数据,重复上传引发校验冲突或覆盖错误。
正确做法是:上传前先查服务端该分片是否已存在(通过分片编号 + 文件唯一标识),仅对未成功上传的分片发起请求。
- 重试逻辑里必须包含幂等标识,例如在 HTTP Header 中加
X-Part-Number和X-Part-MD5 - 使用带超时的
context.WithTimeout,每次重试间隔用time.Sleep(1 指数退避 - 记录失败分片的
partNumber和错误码,避免把400 Bad Request(参数错)也当网络问题重试
Go 标准库 multipart/form-data 为什么不适合大分片
它会把整个分片内容读进内存拼成一整块 multipart body,上传 100MB 分片时,multipart.Writer 内部 buffer 就占掉至少 100MB+,GC 压力陡增,还可能触发 OOM。
真正适合的方案是:绕过 multipart,用 bytes.NewReader 或 io.SectionReader 直接把文件某一段暴露给 http.Request.Body,手动设置 Content-Length 和 Content-Type。
- 示例:
req, _ := http.NewRequest("PUT", url, io.NewSectionReader(f, offset, size)) - 务必显式设置
req.Header.Set("Content-Length", strconv.FormatInt(size, 10)) - 如果服务端要求
multipart/form-data,那就只能换小分片(如 5MB),并接受内存开销——没有银弹
分片上传真正的复杂点不在并发本身,而在状态一致性:本地记录、服务端查询、失败恢复、取消逻辑这四者必须对齐。少一个环节,就可能出现“以为传完了,其实缺两片”的情况。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











