net.dialer.timeout仅控制tcp连接建立阶段(三次握手)超时,不影响后续读写;设为0表示无限等待,推荐3–5秒,且必须显式设置,不能替代setreaddeadline等i/o超时控制。

net.Dialer.Timeout 控制的是连接建立,不是读写
很多人设了 net.Dialer.Timeout 还在 conn.Read() 上卡住,是因为这个字段只管 TCP 三次握手完成前的耗时。一旦连接建好,哪怕服务端不发数据、中间网络断开,它就不再起作用。
常见错误现象:net.DialTimeout 返回成功,但后续 conn.Write() 阻塞十几秒才报错;或用 http.Client 只设了 Timeout 却发现 DNS 卡死无响应。
-
Timeout设为 0 表示无限等待,生产环境必须显式赋值,推荐 3–5 秒 - 不要和
conn.SetReadDeadline()混用 —— 后者是每次 I/O 前手动重置,适合服务端长连接管理 - 若用在
http.Transport中,必须通过DialContext注入,直接改DefaultTransport不生效
http.Transport 才是 HTTP 连接复用的实际控制者
Go 的 http.Client 默认开启连接复用,但真正决定是否复用、复用多久、复用多少条的,是背后的 http.Transport。光靠 Client.Timeout 或全局变量无法影响空闲连接行为。
关键配置项之间有依赖关系:比如 IdleConnTimeout 必须大于 ResponseHeaderTimeout,否则连接可能在读响应体前就被回收;MaxIdleConnsPerHost 太小会导致高频请求反复建连。
-
MaxIdleConns控制整个 client 的空闲连接总数,设为 0 表示禁用复用 -
MaxIdleConnsPerHost是 per-host 限制,默认 100,对微服务场景建议调高(如 200) -
IdleConnTimeout推荐设为 60–90 秒,太短增加建连压力,太长占用 fd 和内存 - 如果服务端主动关闭连接(如 Nginx 默认
keepalive_timeout 75s),客户端的IdleConnTimeout应略小于它
自己实现 TCP 连接池前先确认是否真需要
除非你绕过 http.Client 直接操作 net.Conn(比如对接 Redis、MySQL、自定义协议),否则没必要手写连接池 —— http.Transport 已经做了这件事,且更健壮。
手写池子最容易踩的坑是把失效连接放回去:conn.RemoteAddr() != nil 不代表连接可用,Write 可能立即报 write: broken pipe,Read 可能永远阻塞。
- 每次
Get()后建议做轻量探测(如发一个PING或SELECT 1)再使用 -
Put()前必须检查连接状态,可尝试SetReadDeadline后读 1 字节,超时即丢弃 -
sync.Pool不适合存net.Conn,因为它不保证对象存活,且连接有状态、不可复位 - 第三方池如
github.com/jolestar/go-commons-pool要配合健康检查逻辑,不能直接套用
DualStack 和 KeepAlive 生效的前提容易被忽略
DualStack = true 并不等于“强制走 IPv6”,它只是让 DNS 解析同时查 A 和 AAAA 记录,然后按 RFC 6724 规则排序。实际走哪条路径,取决于系统路由表、glibc 版本、甚至内核参数(如 net.ipv6.conf.all.disable_ipv6)。
KeepAlive 在 Go 1.19+ 默认为 0(禁用),必须显式设置正值才启用。但即使设了,Linux 下仍受 /proc/sys/net/ipv4/tcp_keepalive_time 等系统参数影响,Windows 则需注册表开启支持。
- 本地测试时,
tcpdump -i any port 80可验证 keepalive probe 是否发出 - 若服务端防火墙丢弃 probe 包,连接会在第一个 probe 后约 2 小时才断开(取决于系统默认 timeout)
-
DualStack对单个域名有效,但若后端是 VIP 或负载均衡器,其真实后端可能只开 IPv4
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











