Go 的 http.Client 默认复用连接,Keep-Alive 失效主因是连接被提前关闭:服务端返回 Connection: close、客户端手动设 close、响应体未读完/未关闭、服务端空闲超时早于客户端配置。

Go 的 http.Client 默认就复用连接,不需要“实现”长连接——真正要做的,是别把它搞断了。
为什么你的 Keep-Alive 看似没生效?
Wireshark 显示每次都是新 TCP 握手,或 netstat 里看不到复用的 ESTABLISHED 连接,大概率不是 Go 没启用 Keep-Alive,而是以下任一情况提前关闭了连接:
- 服务端返回了
Connection: close(比如 Nginx 在 HTTP/1.0 下默认行为,或显式配置了keepalive_timeout 0) - 客户端请求中手动设置了
req.Header.Set("Connection", "close") -
resp.Body没读完也没Close(),或者没用io.Copy(ioutil.Discard, resp.Body)清空残留响应体,导致连接无法归还 idle pool - 服务端主动关闭空闲连接(如 Apache 的
KeepAliveTimeout设为 5s,而你IdleConnTimeout是 30s,它先动手)
关键 Transport 参数怎么调才不踩坑?
http.Transport 的几个参数常被误配,重点看它们的实际作用和常见陷阱:
-
MaxIdleConnsPerHost:单个 host 最大空闲连接数,默认 100。如果并发请求猛增但值太小,会频繁建新连接;设太高又可能耗尽文件描述符。建议压测后定值,一般 50–200 足够 -
IdleConnTimeout:空闲连接保活时间,默认 30s。这个值必须 小于 服务端的 keepalive 超时(比如 Nginx 的keepalive_timeout),否则连接在归还前就被服务端关了 -
ResponseHeaderTimeout:从发完请求头到收到响应头的上限。若服务端处理慢(如长轮询挂起),这个超时会直接断连——此时应禁用它(设为 0),改用更细粒度的 context 控制 -
TLSHandshakeTimeout和ExpectContinueTimeout:高延迟或弱网环境下容易触发,建议显式设为 5–10s,避免 handshake 卡住整个连接池
长轮询(Server-Sent Events / 推送)不是 Keep-Alive
别把长轮询和连接复用混为一谈。前者是单个请求长期挂起,靠服务端延迟写 body 实现;后者是多个请求复用一条 TCP 连接。调 MaxIdleConnsPerHost 对长轮询完全无效。
立即学习“go语言免费学习笔记(深入)”;
- 服务端必须显式 flush 响应头:
w.WriteHeader(http.StatusOK)→w.(http.Flusher).Flush()→ 等待事件 → 写 body - Nginx 反向代理需调大
proxy_read_timeout(默认 60s),否则它会在服务端还没发数据时主动断开 - 客户端必须用
context.WithTimeout或context.WithCancel构造 request,resp.Body.Close()不能中断阻塞读,只有 cancel context 才能真正唤醒json.NewDecoder(resp.Body).Decode() - 客户端读取时不能只依赖
io.ReadFull或无超时的Read,必须配合ctx.Done()检查,否则 cancel 后仍卡死
最易忽略的一点:服务端 handler 中任何 panic 都可能发生在 Flush() 之前,导致连接泄漏且无日志——务必用 defer 包裹资源清理,但别 defer resp.Body.Close()(那是客户端责任)。


















