http.defaultclient高并发易耗尽文件描述符,因其默认transport无连接数限制且空闲连接30秒后才关闭;应自定义client并配置maxidleconns、maxidleconnsperhost等参数,http/2无需手动设keepalive。

为什么 http.DefaultClient 在高并发下容易耗尽文件描述符
因为它的默认 http.Transport 没有限制连接数,且复用策略保守:空闲连接默认 30 秒后关闭,但新建连接不设上限。QPS 上去后,大量 TIME_WAIT 或未及时复用的连接会迅速占满 ulimit -n。
实操建议:
- 永远不要直接用
http.DefaultClient做服务间调用或爬虫类高频请求 - 显式构造
http.Client并配置自定义http.Transport - 检查当前系统的
net.ipv4.ip_local_port_range和fs.file-max,避免底层被掐住
MaxIdleConns 和 MaxIdleConnsPerHost 到底控制什么
这两个参数不是“最大并发请求数”,而是“最多缓存多少个空闲连接”。MaxIdleConns 是全局总和,MaxIdleConnsPerHost 是单域名(含端口)上限。如果只设了前者没设后者,遇到多 host 场景(比如调用 10 个不同 API),所有空闲连接可能全堆在第一个 host 上,其余 host 反而频繁建连。
常见错误现象:dial tcp: lookup example.com: no such host 看似 DNS 问题,实则是连接池饥饿导致超时重试压垮 DNS 缓存或上游解析服务。
立即学习“go语言免费学习笔记(深入)”;
推荐值(中等负载):
MaxIdleConns: 100MaxIdleConnsPerHost: 100IdleConnTimeout: 30 * time.SecondTLSHandshakeTimeout: 10 * time.Second
HTTP/2 下 KeepAlive 还需要手动开吗
不需要。HTTP/2 默认启用多路复用(multiplexing),连接生命周期由帧级心跳(PING 帧)和 Settings 协商管理。KeepAlive 是 HTTP/1.1 的 TCP 层保活机制,在 HTTP/2 中由客户端和服务端各自控制,Go 的 http.Transport 会自动适配。
但要注意:
- 若服务端强制降级到 HTTP/1.1(比如 Nginx 配置了
http2 off),KeepAlive才生效 -
KeepAlivePeriod参数仅对 HTTP/1.x 有效,HTTP/2 下设了也忽略 - 用
curl -v或 Wireshark 确认实际协商的是 HTTP/2,别只看代码里写了http2.ConfigureTransport
连接池打满后请求卡住,怎么快速定位是池子小还是下游慢
关键看阻塞点:如果 goroutine 堆在 transport.roundTrip 的 getConn 阶段,说明池子空了在等新连接;如果卡在 readLoop 或 writeLoop,大概率是下游响应慢或网络抖动。
实操建议:
- 开启
http.Transport的ResponseHeaderTimeout和ExpectContinueTimeout,避免无限等 header - 用
net/http/pprof抓 goroutine profile,搜索getConn调用栈 - 加一层 metrics:统计
http.Client的RoundTrip耗时分布,区分 “wait for conn” 和 “send + recv” 时间
真正难调的从来不是参数值,而是你不知道连接到底卡在哪一层 —— TCP 握手?TLS 握手?DNS 解析?首字节延迟?这些阶段在 Go 的 trace 日志里都藏得挺深。










