套接字操作超过截止时间返回 i/o timeout 的定位与修复
客户端报 dial tcp <ip>:<port>: i/o timeout、read tcp <ip>:<port>: i/o timeout 或 write tcp <ip>:<port>: i/o timeout,Go 的 net.Error 会满足 Timeout() == true,DNS 解析也可能返回 lookup <name>: i/o timeout。它表示某次套接字操作在本地超时,不表示对端明确拒绝,也不等于对端没有响应。
要结合错误前缀判断阶段:dial 是建连超时,常见于 SYN 被丢弃;read 是连接已建立但在截止时间内没有读到数据;write 是发送缓冲区迟迟无法写入,通常对端读得太慢或窗口为零。不同阶段的根因和修复方向差别很大,不能只看到 i/o timeout 就调大超时。
先用分阶段耗时确认卡在哪里:
curl -sS -o /dev/null -w 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} first_byte=%{time_starttransfer} total=%{time_total}\n' --connect-timeout 3 --max-time 10 https://<host>/
nc -vz -w 3 <host> <port>
ss -tin '( dport = :<port> or sport = :<port> )'
ss -lnt '( sport = :<port> )'
nstat -az TcpExtTCPTimeouts TcpExtTCPLostRetransmit TcpRetransSegs
mtr -n -T -P <port> -c 20 <host>
tcpdump -ni any -tttt 'host <host> and port <port>'
dig +time=3 +tries=1 <host>
journalctl -u <service> --since '30 min ago'
kubectl logs <pod> -n <ns> --previous
connect 有值而 first_byte 为 0,说明 TCP 已建连但服务端或中间层迟迟不回数据;connect 为 0 则连 SYN-ACK 都没回来。ss -tin 里持续增长的重传计数和较大的 Send-Q,分别指向链路丢包与应用没有及时读取。服务端同时看监听队列:Recv-Q 长期非零说明 accept 跟不上,Send-Q 堆积说明对端接收窗口或读取速度有问题。
如果只有大响应超时而小响应正常,优先查路径 MTU 和接收窗口;如果只有新建连接超时而已有连接正常,优先查 SYN 路径、监听队列和中间设备的新建连接限速。
Go 客户端应显式设置各阶段超时,避免默认无限等待:
client := &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 3 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
},
Timeout: 20 * time.Second,
}
curl 的分阶段耗时和 tcpdump 确认卡在建连、TLS、等待响应还是读响应体,再决定排查方向。ping -M do -s <size> 和 tracepath 验证路径 MTU,必要时调整接口 MTU 或做 MSS clamping。ndots 与 CoreDNS 延迟。合上页面,先回答,再展开对照。
i/o timeout 能说明请求一定没有到达服务端吗?curl 显示 connect 耗时为 0,最可能卡在哪一层,应先查什么?i/o timeout?connect 为 0 表示 TCP 三次握手没有完成。先查安全组和防火墙是否放行、路由与返回路径是否正常、服务端监听队列是否溢出,并用 tcpdump 看 SYN 与 SYN-ACK。
小程序版
微信扫码,随时查命令