默认 http.Transport 因 MaxIdleConnsPerHost=2 限制导致高并发时连接频繁新建与泄漏;必须协同配置 MaxIdleConns、MaxIdleConnsPerHost 和 IdleConnTimeout,并始终调用 resp.Body.Close() 才能复用连接。

为什么默认 http.Transport 会导致连接快速泄漏
Go 的 http.DefaultClient 默认只允许每个主机最多 MaxIdleConnsPerHost = 2 个空闲连接。当你并发发起 100 个请求到同一后端(如 localhost:8080),前 2 个可能复用,其余 98 个几乎必然新建 TCP 连接——哪怕响应头带 Connection: keep-alive。更糟的是,只要 resp.Body 没被读完或漏调 Close(),连接就无法归还池中,直接被关闭,端口立即释放又马上被下个请求抢占。
必须显式配置的三个 Transport 参数
仅改一个参数没用,这三个要协同设置:
-
MaxIdleConns:设为 ≥ 预期峰值并发数(如 500),否则全局连接池满后新请求只能等或新建 -
MaxIdleConnsPerHost:关键!必须和MaxIdleConns同量级(如 500),否则单主机连接池仍是瓶颈 -
IdleConnTimeout:建议设为比服务端keepalive_timeout小 10–15 秒(如 Nginx 默认 75s,则设 60s),避免客户端保留已失效连接
示例初始化:
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 500,
MaxIdleConnsPerHost: 500,
IdleConnTimeout: 60 * time.Second,
},
}
resp.Body.Close() 不是可选项,是回收前提
即使你只读 header、甚至只检查状态码,也必须调用 resp.Body.Close()。漏掉它会导致:
- 连接无法归还到 idle pool,后续请求被迫新建连接
- goroutine 泄漏(如果用
io.Copy(ioutil.Discard, resp.Body)但忘了 Close) - 文件描述符缓慢增长,最终触发
too many open files
安全写法是 defer + 显式判断:
resp, err := client.Get(url)
if err != nil {
return err
}
defer resp.Body.Close() // 即使下面 panic 也会执行
// ... 处理 resp
TIME_WAIT 堆积时不能只靠调大端口范围
当 netstat -ant | grep TIME_WAIT | wc -l 超过 2 万,说明短连接模式已失控。此时光调 net.ipv4.ip_local_port_range 或 tcp_tw_reuse 只是掩耳盗铃——根本问题在连接未复用。真正有效的做法是:
- 先确认是否真用了长连接:抓包看是否有大量 SYN → FIN 序列,或检查服务端日志是否频繁记录新连接
- 禁用所有手动设置
req.Close = true或req.Header.Set("Connection", "close") - 验证服务端返回头:确保没有
Connection: close,且Keep-Alive: timeout=60存在
操作系统调参只是兜底手段,代码层复用失效才是主因。











