如何使用Golang实现并发文件下载_Golang HTTP多任务下载示例

15次阅读

应复用单个http.Client并调大MaxIdleConns和MaxIdleConnsPerHost;多goroutine写文件须用WriteAt配合预分配和互斥offset;断点续传需HEAD校验Range支持并分块SHA256校验;并发控制用带缓冲channel,失败按错误类型指数重试。

如何使用Golang实现并发文件下载_Golang HTTP多任务下载示例

并发下载时如何避免 http.Client 被复用导致连接泄漏

Go 的 http.Client 默认复用底层 TCP 连接,但若在并发场景中反复新建 http.Client 实例(比如每个 goroutine 都 new 一个),会快速耗尽文件描述符;反之,若全局只用一个 http.Client 却不设限,又可能因默认的 MaxIdleConnsMaxIdleConnsPerHost 过小,导致大量请求排队等待空闲连接。

正确做法是复用单个 http.Client,并显式调大连接池容量:

client := &http.Client{     Transport: &http.Transport{         MaxIdleConns:        200,         MaxIdleConnsPerHost: 200,         IdleConnTimeout:     30 * time.Second,     }, }
  • 不要在 goroutine 内部创建新 http.Client
  • MaxIdleConnsPerHost 必须设为 ≥ 并发数,否则实际并发量会被 transport 拦截限制
  • 若目标服务器域名分散(如 cdn1.example.com、cdn2.example.com),MaxIdleConnsPerHost 才真正起作用;同一 host 下仍受 MaxIdleConns 总量约束

如何安全地用多个 goroutine 写入同一个文件

直接让多个 goroutine 同时 os.Write 到一个 *os.File 会导致数据错乱或 panic —— 文件偏移量(offset)不是 goroutine 安全的。常见错误是:每个 goroutine 自行计算分块起始位置后调用 file.Write(),结果写入位置互相覆盖。

必须使用 file.WriteAt(),它按指定 offset 写入,且内部加锁保证原子性:

立即学习go语言免费学习笔记(深入)”;

_, err := file.WriteAt(chunkData, int64(startOffset)) if err != nil {     // 处理写入失败,如磁盘满、权限不足 }
  • 确保每个 goroutine 分配到互不重叠的 startOffsetchunkData 长度
  • 提前用 file.Truncate(totalSize) 预分配文件大小,避免稀疏文件和扩容竞争
  • 不要用 file.Seek() + file.Write() 组合,它在并发下不可靠

如何实现断点续传与分块校验

HTTP 下载中断后从头开始太浪费。关键在于:先 HEAD 请求获取 Content-Length,再根据本地已存在文件大小决定是否续传;同时每块下载完应校验 SHA256,防止网络传输损坏。

典型流程:

  • 发起 HEAD 请求,检查响应头 Content-LengthAccept-Ranges: bytes
  • 读取本地文件长度 stat.Size(),若 > 0 且 Content-Length > stat.Size(),则用 Range: bytes=xxx- 续传
  • 每块数据写入前计算 sha256.Sum256(chunkData),与服务端提供的 ETag 或预置 checksum 对比(若支持)

注意:ETag 不一定代表完整文件哈希,有些 CDN 返回的是 inode 或 MD5,不能直接用于分块验证;稳妥做法是服务端额外提供 X-Checksum-Sha256 响应头。

如何控制并发数并处理失败重试

无限制启动 goroutine 容易打爆内存或触发目标服务器限流。应使用带缓冲的 channel 控制并发数,并对失败请求做指数退避重试。

sem := make(chan struct{}, 10) // 最多 10 个并发 for i, task := range tasks {     go func(t DownloadTask, idx int) {         sem <- struct{}{}>

  • 别用 sync.WaitGroup 单纯等 goroutine 结束,它不控并发;sem channel 是轻量且精确的节流方式
  • 重试逻辑里要区分错误类型:dns 失败可立即重试,404 或 416(Range Not Satisfiable)应直接失败,5xx 错误适合指数退避(如 1s、2s、4s)
  • 每次重试前检查本地文件是否已被其他 goroutine 写满,避免重复下载同一块

分块下载看似简单,但 offset 对齐、连接复用、写入原子性、错误恢复这四点任意一个没处理好,都会导致文件损坏或性能骤降。尤其是 WriteAt 的 offset 计算和 http.Transport 参数调优,最容易被忽略。

text=ZqhQzanResources