go的http.client.do默认复用连接,但复用失效主因是干扰连接池:未调resp.body.close()、每次新建client、maxidleconnsperhost过小、服务端返回connection: close或host大小写不一致。

http.Client.Do 默认就复用连接,但复用是否生效,完全取决于你有没有干扰它——不是它不工作,而是你无意中把它关了。
为什么Do看起来没复用连接
现象是每次请求都新建 TCP 连接(netstat 看到大量 ESTABLISHED 或 TIME_WAIT),根本原因不是 Do 本身问题,而是连接池被阻断:
- 漏调
resp.Body.Close():连接卡在“使用中”,永远进不了 idle pool - 每次调用
Do都 new 一个*http.Client:每个 client 持有独立Transport,池子互不共享 - 服务端响应头含
Connection: close或协议是HTTP/1.0:Go client 只能乖乖断连 - 请求 URL 的 host 大小写不一致(如
API.EXAMPLE.COMvsapi.example.com):被视为两个不同 host,各自建池 - 自定义
Transport时没设MaxIdleConnsPerHost:默认值是 100,但若沿用未显式配置的 Transport,实际可能被其他库覆盖为 2
Do 复用依赖的三个 Transport 参数必须显式设
http.Client 是壳,真正管连接的是 http.Transport。不配这三项,等于让连接池裸奔:
-
MaxIdleConns:全局最大空闲连接数,建议 200~500(默认 0 表示无限制,但生产环境需控 fd) -
MaxIdleConnsPerHost:单 host 最大空闲连接数,比上面更关键;建议设为与MaxIdleConns同值(如 200),否则会静默截断成瓶颈 -
IdleConnTimeout:空闲连接存活时间,推荐60 * time.Second;必须略小于服务端keepalive_timeout(如 Nginx 默认 75s),否则对方先断
漏掉 TLSHandshakeTimeout 或 ResponseHeaderTimeout,会导致 goroutine 卡死在握手或等 header 阶段,间接拖垮连接池。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
Do 调用后不关 Body 就等于锁死连接
哪怕你只读 resp.StatusCode,也必须消费并关闭 resp.Body,否则连接不会归还。常见错误写法:
-
defer resp.Body.Close()写在函数开头,但中间return或 panic 导致未执行 - 用
io.ReadAll(resp.Body)读完却忘了.Close() - 只检查状态码就直接 return,body 完全未碰
稳妥做法:_ = resp.Body.Close() 放在读完 body 后立刻执行;或至少用 io.Copy(io.Discard, resp.Body) 清空缓冲区再关。
HTTP/2 下 Do 的复用逻辑略有不同
Go 1.6+ 默认启用 HTTP/2(HTTPS + ALPN 支持),此时复用更彻底:单 TCP 连接支持多路并发请求。但注意:
- 仍依赖同一
Transport实例和 host 一致性 -
MaxIdleConnsPerHost在 HTTP/2 下控制的是“每个 host 的最大空闲流数”,行为上仍是你要调的那个“每域名复用上限” - 若服务端不支持 HTTP/2,Go 自动降级到 HTTP/1.1,复用逻辑回归到 keep-alive 和上述三个参数
- 别手动设
ForceAttemptHTTP2(已废弃),也不用干预NextProto
真正容易被忽略的点:复用不是靠“开了开关”,而是靠 Transport 配置、请求上下文、服务端响应三者严丝合缝。少一个环节,连接就回不了池子。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










