【背景】
做了 15 年网络工程,keepalive 是我见过「默认值最危险、讨论最少」的参数之一。Linux 默认 net.ipv4.tcp_keepalive_time=7200(2 小时)、tcp_keepalive_intvl=75(75 秒)、tcp_keepalive_probes=9,换算下来一条死连接要 2 小时 + 11 分钟 15 秒才会被内核标记为异常。
但在微服务 + LB + 连接池的世界里,这个数字意味着:
- 上游 HikariCP / Apache HttpClient 池里躺着一堆「半死」连接,对端 Pod 已经被 K8s 驱逐、LB 后端已摘除,但内核层根本不知道;
- 下一次业务请求拿到这条连接,发 SYN-ACK 探测超时,P99 直接吃掉整个 keepalive 超时窗口(我见过一次 75s×3 = 225s 的单请求超时,告警都来不及响);
- 更隐蔽的:中间过了一层 LVS/HAProxy,HAProxy 的
timeout client是 30s,内核 keepalive 是 2h——两边各自为政,连接在 HAProxy 侧已经 FIN 了,内核侧还在「活着」,应用层拿到的是 RST 而非正常关闭。
去年我们一个跨机房 RPC 集群,3 个节点之间走 BGP,一次链路闪断 800ms,内核 keepalive 没来得及探(因为还没到 2h 窗口),应用层连接池里 4000+ 条连接全部变成「僵尸」,恢复后 P99 从 12ms 飙到 3.2s,持续了整整 6 分钟(直到连接池的 validationTimeout 才把坏连接清掉)。
【常见分歧 / 几种做法】
我调研了至少 6 套线上系统,发现 keepalive 配置基本分三派:
派别 A:「内核 keepalive 关掉,全靠应用层」
- 做法:
tcp_keepalive_time=0(禁用),HikariCP 配keepaliveTime=30s、validationTimeout=5s,HAProxy 配timeout tunnel 3600s+tcp-check。 - 理由:内核 keepalive 粒度太粗(75s 间隔),不如应用层
SELECT 1/PING帧精准;而且内核 keepalive 包走的是 TCP 头,中间某些云 LB(如 AWS NLB)不转发,反而制造误判。 - 风险:应用层没覆盖到的路径(比如 sidecar、iptables 重定向后的连接)就裸奔了。
派别 B:「内核 keepalive 调到 60s 级别,当兜底」
- 做法:
tcp_keepalive_time=60、tcp_keepalive_intvl=10、tcp_keepalive_probes=3(60s + 30s = 90s 检测窗口)。 - 理由:比默认 2h 好太多,又不像 10s 那样对 LB 产生大量探测流量;应用层 keepalive 是「主动」,内核 keepalive 是「兜底」,两层不冲突。
- 风险:如果 LB 侧
timeout client< 90s,LB 先断了,内核还在发 probe,产生大量 RST,conntrack 表被撑爆(我见过 8000 conn/s 的场景下 conntrack 表 262144 不够用)。
派别 C:「按连接类型分级配」
- 做法:长连接(gRPC / Kafka)用
setsockopt(TCP_KEEPIDLE=30, TCP_KEEPINTVL=10, TCP_KEEPCNT=3),短连接(HTTP 请求)保持默认 2h 不管;LB 侧timeout tunnel统一 300s。 - 理由:一刀切要么误杀(短连接被频繁探测)要么漏杀(长连接 2h 才发现),分级配最合理。
- 风险:配置散落在应用代码、内核 sysctl、LB 配置三处,运维复杂度陡增;一旦某一层改了参数,另外两层不知道。
【我的倾向】
我站 派别 C,但有一个前置条件:先把全链路的超时预算画出来。
具体步骤(我们线上的做法):
- 画一条从客户端 → LB → 后端 Pod 的超时链:应用层超时 > LB timeout > 内核 keepalive 检测窗口 > 对端 LB timeout,确保每一层都「先于」下一层断连,避免出现「我还在发数据,你已经 FIN 了」的竞态。
- 长连接(gRPC / Kafka / JDBC):
TCP_KEEPIDLE=30s、TCP_KEEPINTVL=10s、TCP_KEEPCNT=3(检测窗口 60s),应用层 keepalive 5s 一次 PING,两层取更短的那个生效。 - 短连接(HTTP REST):内核 keepalive 保持默认 2h,靠连接池的
maxLifetime=30min+validationTimeout=3s管理生命周期。 - LB 侧:
timeout client和timeout server统一 120s(比内核检测窗口 60s 大一倍,留余量),retries 2(快速重试到健康后端)。 - 监控:
netstat -s | grep -i "reset"+conntrack -L | wc -l纳入 Prometheus,conntrack 使用率 > 70% 告警。
核心原则:keepalive 不是越短越好,而是「恰好比上一层的超时短、比下一层的超时长」。配错了,它就从保命药变成慢性毒药——不致死,但每次链路抖动都让你 P99 抖一次。
【想听大家聊的】
- 你们线上 keepalive 参数是多少?是统一 sysctl 还是按应用
setsockopt? - 有没有遇到过「内核 keepalive 和 LB timeout 打架」的具体案例?最终怎么解的?
- 云厂商 LB(AWS NLB / GCP LB / 阿里云 SLB)对 TCP keepalive 包的处理有没有差异?我见过 AWS NLB 不转发 keepalive probe 的情况,你们踩过吗?
- 如果让我选一个「如果只能改一个参数,改哪个收益最大」,你们会选哪个?
欢迎拍砖,带数字来。
0赞
0收藏
0楼层