分片下载需服务端支持Range头并返回206,否则会覆盖写入;并发写须精准seek偏移、禁用O_APPEND和O_TRUNC;连接池需调大MaxIdleConnsPerHost;校验须全量或分段hash,不可依赖Content-Length。

用 http.Range 请求文件分片,不是所有服务都支持
HTTP 分片下载依赖服务端返回 206 Partial Content,关键看它是否支持 Range 头。Nginx、Apache 默认开启,但很多 API 网关、对象存储(如部分 MinIO 配置)或自研后端会忽略或拒绝 Range 请求——这时你发了分片请求,实际收到的仍是 200 OK 全量响应,协程间写文件会相互覆盖。
实操建议:
- 先用
curl -I -H "Range: bytes=0-1023" https://example.com/file.zip检查响应头是否有Accept-Ranges: bytes和Content-Range - Go 中发起预检请求,用
HEAD方法验证,不要直接上GET+Range - 若不支持,别硬分片——改用单协程流式下载 +
io.Copy,或换支持断点续传的 CDN
io.Seeker + os.O_WRONLY | os.O_CREATE 是写入分片的最小安全组合
多个协程并发写同一文件,必须确保各自写入指定偏移,不能依赖“追加”(os.O_APPEND),否则位置不可控,内容错乱。
实操建议:
- 打开文件时用
os.O_WRONLY | os.O_CREATE,**不要用os.O_TRUNC**,避免其他协程正在写时被清空 - 每个协程拿到
*os.File后,立刻调用f.Seek(offset, io.SeekStart)定位,再用io.CopyN(f, resp.Body, size)写入精确字节数 - 写完不关闭文件句柄,由主协程统一 close;协程间不共享
resp.Body,每个都独立Read
协程数不是越多越好:http.DefaultTransport.MaxIdleConnsPerHost 会卡住
默认 Go HTTP 客户端只允许每 host 最多 2 个空闲连接,开 10 个协程发请求,8 个会阻塞在连接池排队,实际并发远低于预期,还可能触发超时。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
实操建议:
- 显式配置
http.Transport:把MaxIdleConnsPerHost设为 >= 协程数(比如 20),同时设IdleConnTimeout防止连接堆积 - 用
sync.WaitGroup控制协程生命周期,但别用time.Sleep等待——应等所有io.CopyN返回后再继续 - 注意目标服务器反爬策略:短时间大量 Range 请求可能被限速或封 IP,加
time.Sleep在协程启动前(非写入后)更合理
校验必须在所有分片写完后做,且不能只靠 Content-Length
分片下载完成后,文件大小可能对得上,但中间某一片写错(比如网络中断导致少写了几百字节),Content-Length 无法发现——因为它是按分片请求的 range 算的,不是真实写入结果。
实操建议:
- 每个协程写完后,记录该段的
md5.Sum或sha256.Sum256,主协程汇总后比对服务端提供的全量 hash(如有) - 没提供全量 hash?那就只能本地重算整个文件的 hash,但注意:大文件别用
os.ReadFile,用bufio.NewReader分块读 +hash.Hash.Write - 别在写入过程中校验——seek 和写是两个系统调用,中间可能被中断,校验动作必须是原子的“写完即校”或“全写完再校”
分片下载真正难的不是起协程,而是定位、写入、连接、校验这四个环节的边界条件——每个都容易静默失败,而且一出问题就难以复现。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










