linux服务器tcp keepalive需应用层、内核层、基础设施层协同配置:客户端启用so_keepalive,内核调小tcp_keepalive_time(如540s),确保总探测窗口小于lb/nat超时;否则连接静默断开。

Linux服务器上开启TCP Keepalive不能只靠setsockopt(SO_KEEPALIVE)就完事——数据库客户端或连接池是否启用该选项、内核默认2小时才探测、NAT/负载均衡器提前踢掉连接,这三者不协同,Keepalive基本等于没开。
为什么数据库长连接会“静默断开”
MySQL、PostgreSQL等客户端默认不启用SO_KEEPALIVE;即使启用了,内核默认tcp_keepalive_time=7200(2小时),而云厂商SLB、家用路由器、企业防火墙普遍设置5~30分钟空闲超时。结果就是:连接在中间设备上被悄无声息地回收,但两端TCP状态仍是ESTABLISHED,应用发请求时直接卡住或报Connection reset by peer或Lost connection to MySQL server during query。
必须同时配置的三层参数
单点调优无效,需应用层、内核层、基础设施层同步对齐:
- 应用/连接池侧:确认数据库驱动启用
SO_KEEPALIVE(如MySQL JDBC加?socketTimeout=0&tcpKeepAlive=true;pgBouncer设tcp_keepalive=on) - 内核侧:把
tcp_keepalive_time压到略小于LB超时值(例如LB是600秒,则设为540);tcp_keepalive_intvl建议30;tcp_keepalive_probes设2~3即可(避免拖太久) - 基础设施侧:查清你用的ALB/SLB/NAT网关的空闲超时时间,Keepalive总探测窗口(
idle + probes × intvl)必须严格小于它
如何验证Keepalive真正在工作
别只看sysctl输出,要抓包+查连接状态:
- 用
ss -tni查具体连接:看到keepalive字段且数值非0,说明该socket已启用并加载了自定义参数(如ka_timer:600/30/3) - 在空闲连接上用
tcpdump -i any port <code>端口号and 'tcp[tcpflags] & (tcp-syn|tcp-rst|tcp-fin) != 0'抓包,等待空闲期过后观察是否有ACK探测包发出 - 临时把
tcp_keepalive_time设成60,再telnet连上数据库端口,停3分钟后看是否收到RST——这是最直白的生效验证
最容易被忽略的坑:容器和eBPF环境
在Kubernetes里跑MySQL客户端?Docker默认网络、Cilium/Calico等CNI插件、甚至某些eBPF防火墙规则,都可能拦截或延迟Keepalive探测包。此时/proc/sys/net/ipv4/tcp_keepalive_*改了也没用。解决路径只有两条:
- 在Pod里用
hostNetwork: true绕过容器网络栈(仅测试用) - 改用应用层心跳:比如MySQL的
wait_timeout配合定期执行SELECT 1,或在连接池(HikariCP、Druid)里配validationQuery和testOnBorrow
Keepalive不是银弹,它只管链路通不通;而数据库连接是否“可用”,还得靠应用层主动探活。内核参数调得再激进,也救不了卡死但没退出的MySQL进程。











