默认的 http.Client 新建连接而不重用,根本原因是其底层 http.Transport 的默认配置过于保守:MaxIdleConnsPerHost 默认仅2、MaxIdleConns 默认100、IdleConnTimeout 默认30秒,导致高QPS下连接池迅速耗尽,被迫频繁新建连接。

为什么默认的 http.Client 会新建连接而不重用?
Go 的 http.Client 默认启用了连接池,但实际是否重用,取决于底层 http.Transport 的配置是否匹配目标服务。常见现象是:每发一个请求都看到新 TCP 连接(netstat -an | grep :443 查到大量 ESTABLISHED 或 TIME_WAIT),本质是连接未被复用——原因通常是 Transport 的 MaxIdleConns、MaxIdleConnsPerHost 太小,或 IdleConnTimeout 过短,甚至设置了 DisableKeepAlives: true。
http.Transport 连接池关键参数怎么设才合理?
连接重用不是“开了就行”,得调对几个核心字段:
-
MaxIdleConns:整个客户端允许保持的最大空闲连接数,默认 0(即不限制,但不推荐无限制);建议设为 100–1000,视并发量而定 -
MaxIdleConnsPerHost:每个 host(如api.example.com)最多保留的空闲连接数,默认 2——这是最常被低估的值,高并发下必须调大(如 50 或 100) -
IdleConnTimeout:空闲连接存活时间,默认 30s;太短会导致刚建好就断,太长可能占资源;一般设为 60–90s 较稳妥 -
TLSHandshakeTimeout和ResponseHeaderTimeout虽不直接影响复用,但超时过短会让连接提前中断,间接破坏复用
示例配置:
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 1000,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
},
}HTTP/2 下连接复用和 HTTP/1.1 有啥区别?
Go 1.6+ 默认对 HTTPS 目标启用 HTTP/2,它天然支持多路复用(multiplexing),单个 TCP 连接可并发处理多个请求,不再依赖传统 keep-alive 的串行复用。但注意:
立即学习“go语言免费学习笔记(深入)”;
- HTTP/2 不受
MaxIdleConnsPerHost的“每 host 连接数”限制影响,它更倾向复用已有连接,但前提是 TLS 握手成功且服务端支持 HTTP/2 - 若服务端只支持 HTTP/1.1,仍走传统 keep-alive 流程,此时
MaxIdleConnsPerHost就很关键 - 可通过
curl -v https://example.com看响应头是否有HTTP/2 200,或用http2.Transport显式控制(极少需要)
如何验证连接真的被重用了?
光看代码配置没用,得观测实际行为:
- 用
lsof -i :443 -p <pid>或netstat -anp | grep <your-process-pid>查 TCP 连接数变化:连续发 10 个请求,连接数应稳定在个位数而非涨到 10+ - 启用 Go 的
httptrace,观察GotConn是否频繁触发Reused: true:
trace := &httptrace.ClientTrace{
GotConn: func(info httptrace.GotConnInfo) {
log.Printf("reused: %v, conn: %p", info.Reused, info.Conn)
},
}
req.WithContext(httptrace.WithClientTrace(req.Context(), trace))注意:Reused: false 不一定代表失败——首次请求、连接超时后重建、host 变更等都会导致 new conn,重点看高频请求下的复用率。
真正容易被忽略的是 DNS 缓存和 IP 变更:如果服务端是轮询 DNS(如 Kubernetes Service),http.Transport 默认不缓存 DNS 结果,每次解析可能得到不同 IP,导致连接池按 IP 分桶,复用率骤降。这时需配合 transport.DialContext + 自定义 DNS 缓存,或直接固定 endpoint。


















