4、TCP Keepalive调优:检测死连接
TCP 连接建立之后,如果双方长时间没有数据交互,会发生什么?
很多人的第一反应是「连接还活着」。但现实很残酷——中间的路由器可能重启了,对端进程可能已经挂了,防火墙可能悄悄把空闲连接给掐了。而你的应用层,对此一无所知。
这就是 TCP Keepalive 要解决的问题。说白了,它是一套心跳机制,让内核帮你定期检查:对端到底还在不在?
核心观点:TCP Keepalive 不是应用层的心跳,而是传输层的内核保活机制。它不能替代业务心跳,但能兜底——帮你及时清理那些「看起来活着、实际上已经死了」的连接。
4.1 三个内核参数,一个都不能少
Linux 下 TCP Keepalive 由三个参数控制。我习惯把它们称为「多久查一次、隔多久再查、查几次算死」。
| 参数 | 默认值 | 含义 | 文件路径 |
|---|---|---|---|
tcp_keepalive_time |
7200(秒) | 连接空闲多久后开始发送探测包 | /proc/sys/net/ipv4/tcp_keepalive_time |
tcp_keepalive_intvl |
75(秒) | 每次探测包的发送间隔 | /proc/sys/net/ipv4/tcp_keepalive_intvl |
tcp_keepalive_probes |
9(次) | 连续无响应多少次后判定连接死亡 | /proc/sys/net/ipv4/tcp_keepalive_probes |
默认值有多保守?你算算看:7200 秒(2 小时)才开始探测,然后每 75 秒发一次,连续 9 次没回应才断开。也就是说,一个死连接最长要 2 小时 + 75×9 = 2小时11分15秒才能被清理。
嗯,这个时间窗口,足够你的连接池被僵尸连接塞满。
避坑提醒:我曾经在一个长连接网关项目里吃过这个亏。默认 Keepalive 参数没动,上线第三天连接池就爆了。查了半天才发现,大量对端已经宕机的连接还占着 slot,直到 2 小时后才被回收。从那以后,我改参数的第一件事就是调 tcp_keepalive_time。
4.2 调优策略:从 2 小时到 2 分钟
生产环境怎么调?我给出一个经过多次验证的配置方案。注意,这不是唯一答案,但适合大多数内部网络场景。
# 修改 /etc/sysctl.conf 或 /etc/sysctl.d/99-keepalive.conf
# 空闲 60 秒后开始探测
net.ipv4.tcp_keepalive_time = 60
# 探测间隔 10 秒
net.ipv4.tcp_keepalive_intvl = 10
# 连续 3 次无响应即判定死亡
net.ipv4.tcp_keepalive_probes = 3
# 立即生效
sysctl -p
这个配置意味着什么?
- 连接空闲 60 秒后,内核开始发探测包
- 每 10 秒发一次,最多发 3 次
- 最坏情况下,死连接在 60 + 10×3 = 90 秒内被清理
从 2 小时缩短到 90 秒,提升幅度接近 99%。
我的建议:如果你的服务部署在公网或跨机房链路上,可以把 tcp_keepalive_time 适当放宽到 120~180 秒,避免网络抖动导致误杀。内网环境 60 秒完全够用。
4.3 应用层 vs 内核层:谁来做心跳?
很多人会问:有了 TCP Keepalive,是不是就不用应用层心跳了?
我的回答是:不能替代,但能互补。
| 对比维度 | TCP Keepalive | 应用层心跳 |
|---|---|---|
| 检测粒度 | 传输层,只能判断 TCP 连接是否存活 | 应用层,能判断业务逻辑是否正常 |
| 响应速度 | 依赖内核参数,最小秒级 | 可以做到毫秒级 |
| 资源消耗 | 内核处理,几乎无额外开销 | 需要业务代码处理,有 CPU/内存开销 |
| 适用场景 | 兜底清理僵尸连接 | 实时健康检查、负载均衡 |
我个人习惯的做法是:应用层心跳做快速检测,TCP Keepalive 做最后一道防线。应用层 5 秒没收到心跳就主动断开,TCP Keepalive 则用 60 秒的兜底策略,防止应用层漏掉某些异常场景。
4.3 核心流程:Keepalive 如何工作?
下面这张图展示了 TCP Keepalive 从开始探测到判定死亡的完整流程。我建议你仔细看看,尤其是「探测包重试」这个环节——很多调优的坑都出在这里。
这个流程里有一个容易被忽略的细节:探测包的重试间隔是固定的,不会指数退避。所以如果你把 tcp_keepalive_intvl 设得太小(比如 1 秒),网络抖动时很容易误杀。我一般不会低于 5 秒。
4.4 避坑指南:三个常见问题
调了这么多年 Keepalive,我踩过不少坑。挑三个最常见的说说。
-
改了参数不生效
很多人改了
/etc/sysctl.conf忘了执行sysctl -p,或者改了之后没有重启应用。注意,Keepalive 参数是内核级别的,但应用需要重新建立连接才会使用新参数。已经存在的连接不受影响。 -
防火墙拦截探测包
我曾经遇到一个案例:内网两台机器之间 Keepalive 一直不生效,抓包发现探测包被中间防火墙丢弃了。原因是防火墙把 TCP Keepalive 的探测包(ACK 包)当成了异常流量。解决办法是在防火墙上放行这些包,或者改用应用层心跳。
-
误杀正常连接
如果你把
tcp_keepalive_time设得太短(比如 10 秒),而你的应用本身就有长时间静默的场景(比如推送服务),那正常连接也会被频繁探测。探测包本身没问题,但连续丢包时就会误杀。我的经验是:Keepalive 的 timeout 至少要比应用最大静默时间长 2 倍。
一个小技巧:你可以用 ss -t -o 命令查看当前连接的 Keepalive 计时器状态。输出里的 timer:(keepalive,XX秒) 表示距离下次探测还有多少秒。这是排查 Keepalive 是否生效最直接的方法。
4.5 总结:调优清单
最后,我把本章的核心要点整理成一份清单。你部署的时候可以对照着检查。
| 检查项 | 推荐值 | 说明 |
|---|---|---|
tcp_keepalive_time |
60~180 秒 | 内网 60,公网 120~180 |
tcp_keepalive_intvl |
5~15 秒 | 不要低于 5 秒,避免误杀 |
tcp_keepalive_probes |
3~5 次 | 3 次足够,最多 5 次 |
| 应用层心跳 | 建议保留 | 与 Keepalive 互补,不要替代 |
| 防火墙策略 | 放行探测包 | 避免中间设备丢弃 ACK 包 |
嗯,TCP Keepalive 调优就这些。参数不多,但每个都关系到线上连接的稳定性。你配置完之后,记得用 ss -t -o 验证一下,别让死连接拖垮你的服务。