直接用http.Get下载大文件会OOM,因其将整个响应体读入内存;正确做法是手动构造Range请求并发分块下载,用file.Seek定位+io.Copy写入指定偏移,并通过限速chan控制goroutine并发数。

为什么不能直接用 http.Get 下载大文件
直接调用 http.Get 会把整个响应体一次性读入内存,遇到几个 GB 的文件极易触发 OOM(runtime: out of memory)。更糟的是,它不支持断点续传、无法监控进度、也不能利用多核并行下载——本质是串行阻塞式读取。
真正可行的路径是:手动构造 Range 请求,按块发起并发 HTTP GET,每块独立写入文件指定偏移位置。核心依赖三个能力:io.Seeker 文件随机写、net/http 的 Range 头支持、以及 goroutine 协调。
如何正确设置 Range 请求和写入偏移
HTTP Range 请求必须配合服务端支持(返回 206 Partial Content),且文件需支持随机访问。写入时绝不能用 os.O_APPEND,否则多个 goroutine 写同一文件会相互覆盖或错位。
- 用
os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)打开文件,确保可寻址 - 每个 goroutine 调用
file.Seek(startByte, io.SeekStart)定位到本块起始位置,再io.Copy写入 - Range 头格式为
"bytes=0-1048575"(含首含尾),注意末尾字节号 = start + chunkSize - 1 - 最后一块可能小于 chunkSize,需动态计算 end = min(start + chunkSize - 1, fileSize - 1)
如何安全协调并发 goroutine 并处理失败重试
goroutine 泛滥或无限制并发会打爆服务端连接数,也容易触发客户端 dial tcp: lookup xxx: no such host 或 context deadline exceeded。必须控制并发数,并对单块失败做有限重试。
立即学习“go语言免费学习笔记(深入)”;
- 用带缓冲的
chan struct{}控制最大并发数(如 4~8),每次启动 goroutine 前sem ,结束时 <code><-sem - 每块请求套
context.WithTimeout(ctx, 30*time.Second),避免卡死 - 失败时记录
startByte和错误,放入重试队列;最多重试 2 次,超时则整体失败 - 用
sync.WaitGroup等待所有块完成,不要依赖 channel 关闭判断
如何获取文件总大小并校验完整性
下载前必须知道文件总大小才能分块,但并非所有服务端都返回 Content-Length(比如某些 CDN 或签名 URL)。若 HEAD 请求返回 Content-Length: 0 或无该 header,说明不支持,应直接放弃分块下载。
- 先发
http.Head(url),检查resp.Header.Get("Content-Length") - 若为空或非数字,尝试
resp.ContentLength(自动解析后的 int64 值) - 下载完成后建议做 SHA256 校验:用
crypto/sha256.New()边写边更新 hash,最后比对服务端提供的X-Checksum-Sha256header 或已知值 - 注意:不要在下载过程中频繁
file.Stat()查大小,会干扰写入性能
最易被忽略的是 Range 边界计算溢出和文件打开模式——用 os.O_TRUNC 会清空文件,用 os.O_APPEND 会导致写偏移错乱,这两类错误在大文件下很难复现,但一旦发生就无法修复。


















