http连接未复用的主因是未正确配置http.transport参数及未调用resp.body.close()。需设置maxidleconns、maxidleconnsperhost(关键)、idleconntimeout,并确保服务端idletimeout≥客户端idleconntimeout,且每次请求后必须关闭resp.body。
为什么连接没复用,明明用了默认 http.client
go 的 http.client 默认确实启用 keep-alive,但「启用」不等于「复用成功」。常见失效场景包括:
- 每次请求都新建
&http.Client{}或直接用http.DefaultClient:Transport 未共享,每个 Client 拥有独立连接池 - 服务端返回了
Connection: close(比如 Nginx 对 4xx/5xx 响应默认加该头) - URL 中 host 大小写不一致(
api.example.com和API.EXAMPLE.COM被视为不同 host) - 没调用
resp.Body.Close():连接卡在“使用中”,无法归还池子——这是最高频原因
必须配置的三个 http.Transport 参数
http.Transport 才是连接池本体,Client 只是壳。不设这三项,等于没调优:
-
MaxIdleConns:全局最大空闲连接数,建议 200~500;若只调一个后端,设 0(不限制)也常见 -
MaxIdleConnsPerHost:单 host 最大空闲连接数,比上面更关键;默认值仅 100,高并发下易耗尽,一般设 100~200 -
IdleConnTimeout:空闲连接存活时间,推荐60 * time.Second;需略小于服务端keepalive_timeout(如 Nginx 默认 75s),避免被对方先断
注意:MaxIdleConnsPerHost 不能超过 MaxIdleConns,否则会被静默截断。
resp.Body.Close() 是硬性要求,不是习惯
哪怕你只读 resp.StatusCode,也必须关闭 body,否则连接永远无法复用:
- 错误写法:
data, _ := io.ReadAll(resp.Body)后没Close() -
defer resp.Body.Close()在 panic 或提前 return 时可能不执行 - 更稳妥的是读完立刻关:
_ = resp.Body.Close(),或包一层defer func() { _ = resp.Body.Close() }() - 如果只读 header,用
io.Copy(io.Discard, resp.Body)再关,防止缓冲区未消费导致连接滞留
服务端 IdleTimeout 必须和客户端协调
Go 的 http.Server 默认 IdleTimeout 为 0(无限空闲),生产环境必须显式设置:
- 推荐值:90~120 秒,但必须 ≥ 客户端的
IdleConnTimeout - 若服务端
IdleTimeout小于客户端设置,连接会在空闲期被服务端主动断开,导致客户端复用失败并报read: connection reset by peer或类似错误 -
ReadTimeout和WriteTimeout不影响 Keep-Alive 复用逻辑,只控制单次请求/响应的读写时限
真正影响复用成败的,是两端 IdleTimeout 与 IdleConnTimeout 的配合关系,而不是是否“开了 Keep-Alive”。











