Go的http.Client默认复用连接,关键在于避免复用被干扰:需显式配置MaxIdleConns、MaxIdleConnsPerHost和IdleConnTimeout,全局复用单例client,务必调用resp.Body.Close(),并确保服务端支持Keep-Alive或HTTP/2。

Go 的 http.Client 默认就复用连接,不用自己写池子;真正要做的,是别让它失效。
为什么复用看起来没生效
常见现象:压测时 netstat -an | grep TIME_WAIT 连接暴涨、QPS 上不去、报错 net/http: request canceled (Client.Timeout exceeded while awaiting headers)——这些往往不是没复用,而是复用被悄悄打断了。
- 服务端返回
Connection: close(用curl -v看响应头就能确认) - 客户端漏调
resp.Body.Close(),连接卡在“使用中”状态,无法归还池子 - 手动设置了
req.Close = true或写了req.Header.Set("Connection", "close") - 请求 URL 的 host 或 scheme 不一致(比如混用
https://api.example.com和https://API.EXAMPLE.COM)
必须显式配置的三个 Transport 参数
http.Transport 才是连接池的实际管理者,默认值只适合玩具场景。高并发下不调参,等于开着油门踩刹车。
-
MaxIdleConns:全局最大空闲连接数,建议设为200~500(默认100) -
MaxIdleConnsPerHost:单个 host(如api.example.com)最多缓存多少空闲连接,建议与MaxIdleConns同值(默认也是100,但不设会成为瓶颈) -
IdleConnTimeout:空闲连接保活多久后关闭,推荐60 * time.Second~90 * time.Second;注意要略小于服务端的keepalive_timeout(Nginx 默认75s,这里设60s更安全)
示例:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 300,
MaxIdleConnsPerHost: 300,
IdleConnTimeout: 60 * time.Second,
TLSHandshakeTimeout: 5 * time.Second,
},
}
resp.Body.Close() 不是可选项
这是最隐蔽、最高频的复用失败原因。哪怕你只取 resp.StatusCode,也必须关掉 body。
- 错误写法:
resp, _ := client.Get(url); data, _ := io.ReadAll(resp.Body)—— 忘了resp.Body.Close() - 看似稳妥的
defer resp.Body.Close()在 panic 或提前return时可能不执行 - 更稳妥的是读完立刻关:
defer func() { _ = resp.Body.Close() }()或直接resp.Body.Close()放在读操作之后 - 如果只读 header,用
io.Copy(io.Discard, resp.Body)再关,避免 body 缓冲区未消费导致连接滞留
HTTP/2 复用不用配,但有条件
HTTP/2 自动启用多路复用,单 TCP 连接并发发多个请求,但不是无条件触发。
- 必须用
https://协议(HTTP/2 over TLS 是强制的),http://永远不会升级 - 服务端需支持 ALPN,并返回有效 TLS 证书(自签名证书要手动加进
TLSClientConfig.RootCAs) -
MaxIdleConnsPerHost在 HTTP/2 下失效,但IdleConnTimeout仍控制底层 TCP 连接生命周期 - 别碰
ForceAttemptHTTP2(已废弃),也别手动设NextProto,Go 1.6+ 默认就支持
复杂点在于:HTTP/2 的复用逻辑藏在协议层,你没法像 HTTP/1.1 那样靠调参“感知”到连接是否真的被复用了;最容易忽略的是证书验证失败导致降级回 HTTP/1.1,而你完全没意识到。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










