在 Linux 系统里,网络连接超时并不总是因为链路中断。很多时候服务端日志里密密麻麻的 timeout 异常,背后是 TCP 协议栈的默认行为与应用场景不匹配。比如使用了默认 NAT 的容器环境,或者跨机房调用时存在轻微丢包,内核按照保守策略重传,应用层等待时间远超预期就会报错。理解这些机制并做针对性调整,往往比改造代码更有效。

一、先确认超时发生在哪一层
排查的第一步是用工具看清楚连接状态,而不是盲目改参数。ss 命令比 netstat 更轻量,能直接显示重传队列和定时器信息。如果看到大量处在 SYN_SENT 或 ESTAB 但带有 retrans 计数的连接,说明问题在内核协议栈重传,而不是业务线程阻塞。
可以用下面这条命令持续观察:
# 查看带重传的 TCP 连接 ss -tanp state syn-sent ss -tanpo | grep -i retrans
另外,通过 dmesg 或 /var/log/messages 检查是否有 TCP: out of memory 或 nf_conntrack: table full 的提示,这类内核日志能直接指出资源耗尽型超时。确认清楚层次之后,再决定是调协议参数还是扩连接跟踪表。
二、调整 TCP 保活与重传参数
Linux 默认的 tcp_keepalive_time 是 7200 秒,也就是两小时才探测一次空闲连接。在经过了 NAT 或负载均衡的设备上,会话可能十几分钟就被悄悄丢弃,应用还以为连接活着,发数据时才发现已超时。把保活时间缩短,能让死连接更快被发现并重建。
临时修改可以用 sysctl 命令,永久生效则写入 /etc/sysctl.conf。示例如下:
# 临时生效 sysctl -w net.ipv4.tcp_keepalive_time=600 sysctl -w net.ipv4.tcp_keepalive_intvl=30 sysctl -w net.ipv4.tcp_keepalive_probes=3 sysctl -w net.ipv4.tcp_retries2=8 sysctl -w net.ipv4.tcp_syn_retries=3 # 永久写入配置文件 cat >> /etc/sysctl.conf <<EOF net.ipv4.tcp_keepalive_time=600 net.ipv4.tcp_keepalive_intvl=30 net.ipv4.tcp_keepalive_probes=3 net.ipv4.tcp_retries2=8 net.ipv4.tcp_syn_retries=3 EOF sysctl -p
tcp_retries2 控制数据报文重传次数,默认 15 次在弱网环境下会让应用等待十几分钟才报错,改成 8 次大约几十秒就会通知上层超时,便于快速失败重试。tcp_syn_retries 则决定建连阶段 SYN 的重发次数,调小可避免启动期卡死。这些改动不需要重启服务,对业务透明。
三、处理 conntrack 表满导致的隐性超时
如果机器开了 iptables 或用了 Docker 网桥,内核的 nf_conntrack 模块会记录每条连接。表大小默认往往只有几万,高并发短连接场景很容易写满,新连接直接被丢弃,表现为随机超时。用下面命令可查看使用量:
# 查看 conntrack 当前数量与上限 cat /proc/sys/net/netfilter/nf_conntrack_count cat /proc/sys/net/netfilter/nf_conntrack_max
当 count 接近 max,就需要扩容或者优化规则。扩容方式如下:
# 临时扩容到 30 万 sysctl -w net.netfilter.nf_conntrack_max=300000 echo 300000 > /proc/sys/net/netfilter/nf_conntrack_max # 缩短已建立连接的跟踪超时,释放空闲条目 sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=600
同时建议在 iptables 的 raw 表中对不需要跟踪的流量加 NOTRACK,比如内部健康检查的端口,能显著减少表占用。修改后观察一段时间,如果超时频率明显下降,说明根因就在这里。
四、应用层与系统层配合的兜底策略
即便内核参数合理,也建议在应用里设置合理的连接超时与重试间隔,不要无限等待。以 Python 的 requests 为例,显式传 timeout 参数比依赖系统默认更安全:
import requests
try:
# 连接超时 3 秒,读取超时 5 秒
resp = requests.get('http://192.168.0.1:8080/api', timeout=(3, 5))
print(resp.status_code)
except requests.exceptions.Timeout:
# 这里可以走重试或降级逻辑
print('连接超时,触发兜底')
系统层把重传周期压缩,应用层把等待上限收紧,两层叠加能避免请求堆积雪崩。最后用监控把 TCP 重传率和 conntrack 使用率画成曲线,后续再出现波动就能在超时爆发前介入。