Go HTTP连接池TIME_WAIT爆表主因是MaxIdleConnsPerHost默认值2过小且resp.Body未读完,导致复用失效;需调大该值并确保响应体被完整读取或丢弃。

不是长连接导致TIME_WAIT爆表,是连接池配错 + 响应体没读完共同触发的复用失效。
MaxIdleConnsPerHost设为2就等于主动制造TIME_WAIT
Go标准库http.DefaultTransport的MaxIdleConnsPerHost默认值是2——这意味着每个目标域名(比如api.example.com:443)最多只缓存2个空闲连接。当QPS远超2(比如500+),其余请求只能新建TCP连接,用完即关,每条都进TIME_WAIT。
- 单后端服务(如InfluxDB、自建API)建议直接设为
MaxIdleConnsPerHost = 100或更高,匹配预期峰值QPS - 必须同步设置
MaxConnsPerHost ≥ MaxIdleConnsPerHost,否则连接池会阻塞等待,反而引发超时和goroutine堆积 - 避免
MaxConnsPerHost远大于MaxIdleConnsPerHost:前者管上限,后者管常驻;失配会导致连接建了不用、立刻销毁,加剧TIME_WAIT
resp.Body.Close() ≠ 读完响应体
调用resp.Body.Close()只是关闭流,不等于消费数据。Go的http.Transport复用连接的前提是:响应体被完整读取或丢弃。否则它认为连接可能残留未读数据,拒绝复用,强制关闭连接。
- 无论是否需要body内容,都必须显式处理:用
io.Copy(io.Discard, resp.Body)(Go 1.19+)或io.Copy(ioutil.Discard, resp.Body)(旧版) - 若用
json.NewDecoder(resp.Body).Decode(&v),它内部会读完;但decode失败后仍要补上io.Copy(io.Discard, resp.Body)清理 - 别依赖
defer resp.Body.Close()就以为万事大吉——关闭本身不等于读完
别用http.DefaultClient,也别全局混用一个*http.Client
直接调http.Get()或http.DefaultClient.Do(),等于把连接池配置权交给默认保守参数:MaxIdleConns = 100是全局总和,不是per-host,且无法针对性调优。
立即学习“go语言免费学习笔记(深入)”;
- 务必显式构造
*http.Client并传入自定义http.Transport - 不同目标域(如S3、本地服务、第三方API)应分client配置,各自独立transport;混用会导致参数冲突、复用率下降
- 注意
IdleConnTimeout(默认90s):太短导致空闲连接过早淘汰,太长则fd占用久;建议设为30–60s,配合业务RTT调整
真正难处理的不是参数数字本身,而是「body是读还是不读」这个动作,在错误路径(如error分支)里经常被跳过——漏掉一次,就多一个TIME_WAIT。


















