该错误本质是本地临时端口耗尽,而非网络故障;Linux默认ephemeral端口仅约32768个,高并发短连接下TIME_WAIT堆积导致端口无法及时释放,加之http.Transport默认MaxIdleConnsPerHost=2严重限制连接复用,进而引发“can't assign requested address”。

为什么 dial tcp: can't assign requested address 不是网络问题而是端口用完了
这个错误不是 DNS 解析失败,也不是目标服务不可达,而是操作系统告诉你:本机没空闲临时端口可用了。Linux 默认的 ephemeral port 范围是 32768–65535,总共不到 32768 个;当你的 Go 客户端每秒发起几百次 HTTP 请求(尤其指向同一 localhost:8088 或固定域名),而连接又没能复用,这些端口就会在 TIME_WAIT 状态里排队释放——但释放速度远跟不上新建速度,结果就是“地址无法分配”。
http.Transport 默认配置为何让连接复用形同虚设
很多人以为开了默认 Keep-Alive 就万事大吉,其实关键卡在两个参数上:MaxIdleConnsPerHost 默认只有 2,MaxIdleConns 默认 100。这意味着即使你并发 100 个 goroutine 去请求 http://localhost:8088/api,Transport 最多只肯为这个 host 缓存 2 个空闲连接;其余请求全得新建 TCP 连接,用完即关,端口飞速耗尽。
- 必须显式调大
MaxIdleConnsPerHost,建议至少设为200或更高(视并发量定) -
MaxIdleConns也得同步调高,否则全局池子满了,新连接仍会被拒 -
IdleConnTimeout设为30 * time.Second以上,避免连接过早被回收 - 别漏掉
KeepAlive和TLSHandshakeTimeout(若走 HTTPS)
客户端复用连接后,还要防住 goroutine 泛滥拖垮自己
光调 Transport 不够——如果上游调用方没控并发,比如一个请求触发 1000 个并发 client.Do(),哪怕每个都复用连接,依然会瞬间打满本地端口池、压垮目标服务或触发系统级限流。
- 用
golang.org/x/sync/semaphore做前置限流,比如sem := semaphore.NewWeighted(50) - 每次发请求前
sem.Acquire(ctx, 1),出错或完成时defer sem.Release(1) - 别用
make(chan struct{}, N)模拟信号量——panic 或 error 分支漏写close就永久失血 - HTTP handler 内部必须套
context.WithTimeout,防止某个慢请求卡死整个池子
真实部署时容易忽略的 OS 层面配合
Go 层调优再好,也绕不开内核限制。常见疏漏点:
立即学习“go语言免费学习笔记(深入)”;
- 检查
net.ipv4.ip_local_port_range,必要时扩到1024 65535(注意避开1–1023的特权端口) - 调小
net.ipv4.tcp_fin_timeout(比如从 60 改为 30),加速TIME_WAIT状态回收 - 确认没开
net.ipv4.tcp_tw_reuse(值为 1),它允许复用处于TIME_WAIT的端口,但需确保时间戳开启(net.ipv4.tcp_timestamps = 1) - 用
ss -s | grep -i "tw"实时看当前TIME_WAIT连接数,比netstat更快更准
端口耗尽从来不是单点问题——Transport 参数、goroutine 并发控制、OS 网络栈三者缺一不可。少配一个,压测时就可能突然崩在 dial tcp: can't assign requested address 上,而且错误日志里根本看不出是哪一层漏了。


















