Go HTTP客户端默认启用Keep-Alive,但需注意默认Transport参数限制及服务端兼容性;必须调用resp.Body.Close()防止连接泄漏,高并发场景应自定义MaxIdleConns、MaxIdleConnsPerHost等参数。

Go HTTP客户端默认就启用Keep-Alive
Go 的 http.Client 默认开启连接复用,底层使用 http.Transport 管理 TCP 连接池,只要服务端也支持 Keep-Alive(现代 HTTP/1.1 服务基本都支持),连接就会被自动复用。
常见误解是“要手动开启”,其实不用——但默认行为有隐含限制,容易在高并发或长时运行服务中踩坑:
-
http.DefaultClient的Transport使用默认值:MaxIdleConns=100、MaxIdleConnsPerHost=100、IdleConnTimeout=30s - 如果请求目标域名多(比如调用几十个不同 API),
MaxIdleConnsPerHost会成为瓶颈,空闲连接被提前关闭 - 若服务端主动断连早于客户端
IdleConnTimeout,客户端可能收到read: connection reset by peer或use of closed network connection
自定义Transport时关键参数怎么设
需要长期稳定调用外部服务(如微服务间通信)时,必须显式配置 http.Transport,否则默认值在生产环境大概率不够用。
重点不是“全调大”,而是按场景权衡:
立即学习“go语言免费学习笔记(深入)”;
-
MaxIdleConns:整个 Transport 可维护的空闲连接总数,建议设为200~500(避免 fd 耗尽) -
MaxIdleConnsPerHost:单个 host(如 api.example.com)最多保留几个空闲连接,建议至少50,若调用同一 host 的 QPS 高,可设到100~200 -
IdleConnTimeout:空闲连接保活时间,建议60s—— 太短导致频繁重连,太长可能卡住已失效连接 -
TLSHandshakeTimeout和ResponseHeaderTimeout必须设,否则 DNS 卡住或服务端不写 header 会导致 goroutine 泄漏
示例片段:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
tr := &http.Transport{
MaxIdleConns: 400,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 60 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 15 * time.Second,
}为什么有时Keep-Alive没生效
现象是每次请求都新建 TCP 连接(抓包看三次握手重复出现),常见原因不是 Go 配置问题,而是服务端或中间件切断了连接:
- 服务端返回
Connection: close响应头(比如 Nginx 默认对 4xx/5xx 响应加这个头) - 反向代理(如 ALB、Cloudflare)禁用了 Keep-Alive,或设置了过短的 idle timeout(低于客户端
IdleConnTimeout) - HTTP/2 场景下,Keep-Alive 概念被流复用替代,但 Go 客户端仍会复用 TCP 连接;若服务端降级到 HTTP/1.1 且未正确设置 header,也可能中断复用
- 代码里手动调用了
resp.Body.Close()之后又继续读resp.Body,触发底层连接异常关闭
长连接场景下要注意连接泄漏
Keep-Alive 复用连接的前提是:每次请求后必须调用 resp.Body.Close()。漏掉这一步,连接不会归还给连接池,最终耗尽 MaxIdleConns,后续请求阻塞在 transport.waitingReadLoop。
尤其注意这些地方:
- error 分支里忘记
Close()(最常见) - 用
io.Copy或json.NewDecoder读 body 后没检查错误,提前 return 导致 Close 被跳过 - 把
resp.Body传给其他 goroutine 异步处理,但主流程仍执行了Close()(造成 double-close 或竞态)
安全写法是用 defer resp.Body.Close(),并在 defer 前判断 resp 是否为 nil。
真正难排查的是:连接没泄漏,但复用率低——往往因为请求间隔不稳定,或服务端 timeout 设置比客户端更激进,这时得结合 net/http/httptrace 打点观察 GotConn 和 ConnectStart 的分布。

















