IdleTimeout才是Keep-Alive空闲超时正解,专控连接无请求时的倒计时;Read/WriteTimeout是per-request硬超时,会中断SSE等长连接;需配合客户端行为与代理配置协同生效。

Go HTTP Server 的 IdleTimeout 才是 Keep-Alive 空闲超时正解
Go 1.8+ 中,http.Server.IdleTimeout 是唯一专用于控制 Keep-Alive 连接空闲时长的字段。它和 ReadTimeout、WriteTimeout 完全不同:后两者是 per-request 的硬性截止,一旦超时就杀连接;而 IdleTimeout 只在连接“完全没新请求进来”时才开始倒计时。
默认值为 0,即不限制空闲时间——这在生产环境非常危险,可能导致 fd 耗尽或连接堆积在 ESTABLISHED 状态迟迟不释放。
- 设太短(如
5 * time.Second):浏览器或客户端还没发第二个请求,连接就被关了,Wireshark 里看到全是新 TCP 握手 - 设太长(如
30 * time.Minute):大量空闲连接占着 fd 和内存,尤其在高并发低频调用场景下易出问题 - 合理值通常在
30 * time.Second到5 * time.Minute之间,需结合客户端行为(比如前端轮询间隔、SSE 心跳周期)来定
为什么删掉 ReadTimeout / WriteTimeout 反而能让长连接更稳
这两个字段会无差别中断任何连接,不管它是不是正在传输数据。对 SSE、长轮询、流式下载等场景,它们是隐形杀手。
例如你设了 WriteTimeout: 10 * time.Second,但 handler 正在每 2 秒往 ResponseWriter 写一次 event —— Go 不会重置这个 timer,第 11 秒一到,整个连接直接断开,前端收到 net::ERR_CONNECTION_ABORTED。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- SSE 接口必须禁用
ReadTimeout和WriteTimeout,只保留IdleTimeout - 如果要防卡死,改用
context.WithTimeout或time.AfterFunc在 handler 内部做逻辑超时,比如“连续 60 秒没写入数据就主动 close” - HTTP/2 场景下这些 timeout 影响变小,但若中间有降级代理(如某些 Nginx 配置),仍可能被拖回 HTTP/1.1 并触发全局 timeout
客户端不配合,服务端再配也没用
Keep-Alive 是双向契约。Go http.Server 默认发 Connection: keep-alive,但若客户端发的是 Connection: close,或者压根没带 Connection 头(比如某些 HTTP/1.0 请求),服务端就会按规范关闭连接。
- 用
curl -v http://localhost:8080测试时,注意看请求头是否含Connection: keep-alive(现代 curl 默认带) - Go 客户端若手动设置了
req.Header.Set("Connection", "close"),会直接绕过复用逻辑 - Nginx 作反向代理时,必须配
proxy_http_version 1.1+proxy_set_header Connection '',否则它会把客户端的Connection头透传给后端,破坏复用 - 服务端日志里打印
conn.RemoteAddr(),连续两个请求输出相同地址,才是真复用
Transport 层空闲连接池参数必须显式调大
Go http.Client 默认启用 Keep-Alive,但它的连接池默认极保守:MaxIdleConnsPerHost 是 100,IdleConnTimeout 是 30 秒。微服务间高频调用时,很容易因池满或过期导致新建连接。
- 调高
MaxIdleConnsPerHost(如 200),避免并发稍高就 fallback 到新建 TCP -
IdleConnTimeout建议设为 60–90 秒,比服务端IdleTimeout略长一点,防止客户端提前关连接 - 别用
http.DefaultClient:它不可配置,且全局共享,多个模块共用容易互相干扰 - 务必确保每次请求后都调
resp.Body.Close(),否则连接无法归还 idle pool,池子会慢慢枯竭
真正难的不是设哪个字段,而是理解每个 timeout 的作用域——是 per-request、per-connection 还是 per-handler;最容易被忽略的是反向代理和客户端 header 的隐式破坏,它们让所有服务端配置都变成摆设。

















