两层保活:机器还在吗,程序还能干活吗
一条闲着的长连接,没法告诉你对面是否还存在。而"活着"其实是两个不同的问题,两层保活各答一个——谁也替不了谁。
两种死法。 (1) 机器或路没了:宕机、断网——外加一个隐形的变体:这条连接的 NAT 映射过期了(见 how-nat-works),路悄无声息地不存在了。(2) 机器好好的,程序不行了:进程卡死、死锁、事件循环堵住。操作系统健康,应用不干活。
第一层——TCP keepalive——查机器和路。 内核在闲置够久后自发探测包,对面的内核回话。这证明了主机和路由还在。它看不见死法 (2):进程卡成僵尸,内核照样替它回探测,你会以为一切正常。它的另一个毛病:默认第一次探测在闲置两小时后才发——真靠它的应用都要把这个数调小。
第二层——应用层心跳——查程序本身。 应用自己定时交换小帧(WebSocket 的 ping/pong、HTTP/2 的 PING、gRPC 的 keepalive)。只有对面的应用能回话,所以回话即证明它的事件循环在转、它接得住活。这一层还兼第二份工:周期性流量把 NAT 映射和负载均衡的闲置计时器不断续上,连接不至于被中间设备回收。WireGuard 那个 25 秒的 persistent keepalive,主要就是为这第二份工存在。
出没场景。 数据库连接池(池中连接夜里被中间设备掐死、第二天被当好连接发出去——池的探活与调过的 keepalive 就是来提前剔除它的);云负载均衡(闲置超时是定数、执行时不通知任何人——心跳间隔比它长,连接就无声无息地死);移动端即时通讯(靠心跳发现进隧道、切网络的断连并驱动重连;"在线"绿点就是这一层被直接画到了界面上);gRPC 服务间长连接(两边 ping 参数要对齐,客户端太积极会被服务端以"ping 太多"掐掉);物联网设备群(心跳兼任设备名册的保鲜信号)。
间隔怎么平衡——先三条约束,再三个旋钮。 发现延迟 ≈ 间隔 + 判死窗口,成本 ≈ 单跳大小 × 频率 × 机队规模。间隔不是自由选的:(a) 路径上最没耐心的中间设备定死上限——心跳必须勤过全程最短的闲置超时(NAT 映射、LB 的线),不然连接照样静默死亡;(b) 业务能容忍多晚发现死亡定目标——在线状态晚一分钟没事,选主接管晚十秒是事故;(a)(b) 谁严听谁;(c) 机队规模下的总成本——一百万台设备 30 秒一跳,就是每秒约 3.3 万条纯心跳开销,手机把全机应用合并成一条系统推送通道就是这个账逼的。在约束之内再调:连续漏 2–3 拍才判死(一拍多半是天气,不是死亡);全体加 ±10–20% 随机抖动,免得机队整齐划一地同时发、在服务器上每间隔砸出一个小洪峰;重连退避与心跳分开走,免得服务器刚恢复就被全队同时重连再踩死一次。经验配方:间隔取路径最短闲置超时的五到八成,漏 2–3 拍判死,开抖动,重连走退避。