背景知识
如果你的请求超时,甚至处于read/write阶段的连接突然没了(被drop,客户端无法感知),跟连接空闲时长没太大关系,可能是你的网络组件如ovs-agent(Open vSwitch Agent)自身的bug或者跟ovs版本不兼容导致的,此种情况需要升级ovs-agent解决。
为什么防火墙要断开空闲连接
防火墙资源有限(如会话表、内存等)。长时间空闲的连接会占用资源,影响其他正常连接。
防火墙断开连接的常见方式
防火墙可能通过以下方式终止连接:
- 丢包(Drop):是为了增强安全性,避免暴露主机或网络拓扑信息。防火墙直接丢弃数据包,而不发送任何反馈(如 TCP RST 或 ICMP 错误消息)
- 显示拒绝:防火墙发送 TCP RST(重置连接)或 ICMP 消息(如 “Destination Unreachable”)
对于丢包的方式,客户端无法感知连接已经断开,仍然尝试发送数据包。TCP 协议会认为网络拥塞导致数据包未到达,于是进入重传和退避机制,直到超时。
对于显式拒绝的方式,客户端立即感知到连接断开,释放资源。
防火墙/NAT 设备如何判断连接是否活跃?
大部分防火墙/NAT 设备使用会话超时机制来决定是否清除连接,主要依据:
- 有无数据流量(Data Traffic)
设备通常认为只有真实的数据包(如 HTTP、WebSocket、数据库查询)才算“活跃”。 - 是否使用 TCP KeepAlive
部分设备会忽略 TCP KeepAlive,因为它是协议级别的“空包”,不会被认为是真正的应用流量。 - 是否使用 NAT 映射(NAT Table)
NAT 设备通常会跟踪 TCP 连接,并在长时间无流量时回收 NAT 端口映射,导致连接被强行中断。
TCP KeepAlive 对防火墙/NAT 的影响
设备类型 | 是否认可 TCP KeepAlive | 典型超时时间 |
---|---|---|
Linux 内核 | ✅ 认可,可防止连接断开 | 2 小时(默认) |
企业级防火墙(如 Cisco、Palo Alto) | ❌ 可能不认可(默认忽略) | 5-30 分钟 |
云厂商负载均衡(AWS ELB, Azure ALB) | ❌ 不认可,仍会超时 | 4-10 分钟 |
NAT 设备(如家用路由器) | ❌ 可能不认可 | 5-15 分钟 |
结论:TCP KeepAlive 可能会被 NAT 设备、防火墙忽略,导致连接仍然会超时。
如何让防火墙/NAT 设备认可连接是活跃的?
✅ 方法 1:应用层心跳(HTTP/WebSocket/数据库)
发送真实的应用层请求,如:
- HTTP:每隔 30 秒 GET /ping
- WebSocket:发送 ping 帧
- 数据库:SELECT 1
import time
import requests
while True:
try:
requests.get("http://example.com/ping", timeout=5)
except Exception:
pass
time.sleep(30) # 30 秒一次
问题描述
网络环境中的防火墙或者代理层,可能会对一些空闲达到一定时长的连接进行丢弃。客户端无法感知连接已经断开,仍然尝试发送数据包。TCP 协议会认为网络拥塞导致数据包未到达,于是进入重传和退避机制,直到超时。
为什么是15分钟
RTO:Retransmission Timeout(重传超时)。Linux2.6+ 的TCP RTO_MIN=200ms, RTO_MAX=120s,每次超时时间是增加1倍。
每次超时时间为(秒):0.2, 0.4, 0.8, 1.6, 3.2, 6.4, 12.8, 25.6, 51.2, 102.4, 120.0, 120.0, ……
计算重试次数需要+1,因为第一次正常请求就是0.2,例如重试2次,实际超时时间为 0.2 + 0.4 + 0.8 = 1.4秒(第一次正常发起请求等待0.2秒,第一次重试的请求等待0.4秒超时,依此类推)
可以使用以下命令查看当前操作系统关于tcp keepalive时长和重试的一些配置。
# 默认7200秒
sysctl net.ipv4.tcp_keepalive_time
# 默认重试15次
sysctl net.ipv4.tcp_retries2
解决方案
可以考虑缩短操作系统的tcp保活时间(小于防火墙断开空闲连接的时长)。
如果希望更快地检测到连接问题,可以缩短 tcp_retries2 的值。
参数说明:
当一个 TCP 连接长时间没有数据传输时,系统不会主动关闭它,而是等到 tcp_keepalive_time 设定的时间后,开始发送 TCP Keepalive 探测包。如果对方正常响应,连接就会继续保持;如果对方无响应,系统会继续发送一定次数的探测包(由 net.ipv4.tcp_keepalive_probes 控制,每次间隔tcp_keepalive_intvl秒),如果还是无响应,就会关闭连接。
# 缩短tcp_keepalive_time(小于防火墙断开空闲连接的时长)。
sudo sysctl -w net.ipv4.tcp_keepalive_time=180
sysctl -w net.ipv4.tcp_keepalive_intvl=60
sysctl -w net.ipv4.tcp_keepalive_probes=3
# 减小tcp重试次数8次大约是13秒
sudo sysctl -w net.ipv4.tcp_retries2=5
如果你是k8s pod环境没权限改,可以通过initContainer的方式修改(如果图方便可以直接修改container特权模式启动,在启动命令里修改这些参数):
kind: Deployment
apiVersion: apps/v1
spec:
template:
spec:
initContainers:
- command:
- sh
- -c
- |
sysctl -w net.ipv4.tcp_keepalive_time=180
sysctl -w net.ipv4.tcp_keepalive_intvl=60
sysctl -w net.ipv4.tcp_keepalive_probes=3
sysctl -w net.ipv4.tcp_retries2=5
image: busybox:stable
imagePullPolicy: Always
name: setsysctl
resources: {}
securityContext:
privileged: true
也可以考虑应用层增加健康检查机制,此种方案需要改造连接池的逻辑。