
AI 擅长的是'优化',人类擅长的是'创造'和'关怀'——别去 AI 的主场跟它拼,要去它的盲区。
当 AI 能做完所有'有标准答案'的事,人类的价值,就只剩下那些'没有标准答案'的事——爱、选择、意义、责任。
一、有心跳=能发现死连接,是个错觉
1.1 为什么 TCP 自己发现不了断开
二、心跳发现不了 / 会误判的几种场景
2.1 写得出去,不等于对端活着
2.2 NAT 与中间设备的"假死"连接
2.3 进程被冻结,socket 还开着
2.4 间隔与超时配比不当
2.5 单向心跳的盲区
三、大厂如何设计
四、如何优化提升
很多人对心跳的第一印象是:客户端每隔一段时间发个小包,服务端定时检查,谁不发就踢掉——死连接不就被发现了吗?但真在生产环境跑过一阵接入层就会发现:有心跳机制的系统里照样躺着一堆"显示在线、其实推不动消息"的死连接,心跳并没像直觉承诺的那样把它们都揪出来。
这不是说心跳没用——它是接入层判活的主力探针。问题是如果把"装了心跳"等同于"能发现所有死连接",这里就有一个盲区问题。今天我们讲讲:心跳能发现什么、发现不了什么、怎么补。
特别说明一下,这里我们说的是应用层心跳:客户端按约定间隔往长连接上发个极简探测包;服务端为每条连接维护"最近一次收到数据的时刻",一旦超过某窗口还没收到任何数据,就触发读超时,判这条连接可能死了。

应用层心跳的判活模型:服务端不直接"问对端活没活",而是观察"连接最近还有没有数据进来",用空闲时长--间接推断死活。
注意这个间接推断有个隐含前提:"只要对端活着就一定会按时把心跳送达"。它在不少场景下并不牢靠,盲区正是从这里来的。
既然用的是 TCP,它不是号称可靠、连接断了会主动告诉我?关键在于 TCP 的"可靠"只在有数据传输时才探得到对端:连接空闲、双方都没数据要发时,中间网络断了或对端掉电,没有任何机制会主动通知另一端。于是出现半开连接(half-open):你这边 ESTABLISHED 看着好好的,往里写才发现送不出去。TCP 自带的 KeepAlive 也不顶用,默认间隔以小时计、对 IM 约等于没有。所以必须自做应用层心跳。
这也是业界共识:基于 TCP 的移动端 IM 仍然需要自己做应用层心跳。但心跳只是把"发现不了"的概率降下来,远没到"全都能发现"。
心跳的判活逻辑能浓缩成一句话:收到数据 = 活着,长时间没数据 = 死了。但它要同时满足三个彼此打架的硬约束——
下面列举5个场景问题,本质都是这三角约束在不同场景里打架。
这是最隐蔽、最反直觉的一类。服务端推消息往连接上一写,write 成功返回——很多实现就此认为"连接活着、消息送到了"。但写成功只代表数据进了本机 TCP 发送缓冲区,最多加上对端内核回了 ACK,并不代表对端 App 真收进去了。
设想 App 进程卡死(主线程死锁、OOM 前剧烈 GC),但 socket 没被回收。这时对端内核还活着、TCP 协议栈照常工作——你发的数据照收照回 ACK,只是全堆在内核缓冲区、App 层一字节没消费。从服务端看写操作一路绿灯、有 ACK 回来,怎么看都"活着",可对端应用早冻死。更糟的是心跳和写操作同时失灵——客户端那条心跳逻辑也卡在同一个死进程。

"写成功"只覆盖到对端内核、覆盖不到对端应用。内核回 ACK 给了服务端"对端还活着"的错觉,而真正该收消息的 App 层早已停摆。
所以:别拿"写没报错"当作"对端活着"的证据。
第二类盲区来自中间设备,主要是运营商 NAT 网关。NAT 维护一张连接映射表,一条连接一段时间没数据流动,它会悄悄回收表项,不通知客户端也不通知服务端,而 NAT 超时常在几分钟量级。回收之后两端 TCP 状态机都还停在 ESTABLISHED、双方都以为连接通着,但中间那条路已断——这就是典型的"假死"连接:表面活着,任一方发数据都到不了对面。
心跳能救场吗?部分能,但不是立刻。心跳间隔若比 NAT 超时还长,从 NAT 回收到下一次心跳之间连接已死而服务端浑然不觉,要等下一次心跳石沉大海才暴露。及时性取决于心跳间隔有没有卡在 NAT 超时之内。
第三类是移动端特有的硬骨头:系统级进程冻结。Android Doze、各厂商省电策略、iOS 后台挂起,共同效果是 App 退到后台一段时间后系统冻结进程、定时器停摆、线程不再调度,那段发心跳的代码根本不执行。但关键是——进程被冻结时它持有的 socket 通常没被关闭,连接还挂着。
从服务端看这和"连接死了"一模一样:一段时间收不到数据,判读超时、当死连接清掉。可用户只是把 App 切了后台,过会儿点开还想用——却已被踢下线。这就翻转到另一面:把活着(只是在后台)的连接误杀。
这类问题没有万全之策,主要靠组合拳解决:客户端尽量延长后台存活、厂商推送通道兜底唤醒、服务端对后台连接给更宽松的超时。
前三类是场景的坑,这一类是参数的坑,最易被自己挖。心跳有两个关键参数:客户端心跳间隔、服务端读超时窗口,配不好两个方向都出事:
配比方向 | 后果 |
|---|---|
间隔 / 超时太长 | 死连接滞留久;NAT 可能在两次心跳之间就回收连接,心跳"够不着" |
间隔 / 超时太短 | 偶发一次抖动就误杀活连接;高频心跳还费电、费流量、压服务端 |
有个常被忽略的约束:服务端读超时窗口必须明显大于客户端心跳间隔,一般留 2 到 3 倍余量——心跳包在弱网下晚到几秒太正常,窗口只比间隔大一点,一次抖动就把活连接误判成死。
那间隔到底取多大?个人经验:心跳间隔必须小于 NAT 超时,否则后台连接还没下一次心跳就被运营商回收。微某信团队以前公开过一组实测——三大运营商5G之前回收时间大约 5 分钟左右。这么大的差异恰恰说明不存在对所有网络都最优的固定值:取保守值在长超时网络上白白费电,取激进值又跨不过短超时网络。
最后一类盲区关乎心跳的"方向"。很多实现的心跳是单向的:客户端发,服务端收并刷新计时器、不一定回包。服务端能判断"客户端还在发",但反过来——客户端怎么知道服务端那条下行通道是好的?
考虑一个不对称故障:上行正常、下行坏了。客户端心跳照常发出、服务端照常收到判它在线,可推下来的消息一条收不到。单向心跳完全照不到这种半边坏,因为它探测的恰好是好的那一边。补法是做成双向确认:客户端靠"收到了回应"而非只靠"发出去了"判活,任意方向连续若干次不通都视为异常。
总结起来,规律很清楚:心跳判活的可靠性,取决于"那条发心跳的代码"和"它走的网络路径"是否正常——一旦 App 卡死、进程冻结或单向坏,前提便塌。
单独一次心跳的成败不足以下结论——网络抖一下、安卓 IP 续约 Bug、后台被冻结一小会儿都会让一次心跳失败,但连接并没真死。
它的对策不是把超时调长,而是"成功一次即认定,失败连续累积多次才认定":后台稳定期一次心跳失败后不立刻判死,而用"延迟心跳测试"连测五次,有一次成功就保持原状、五次全失败才重算重连;新建连接还要先用短心跳成功维持三次才切长心跳。这套"成功宽容、失败严格"的非对称判定,把判死从"一次说了算"改成"多次累积才算",正是对"单次信号会骗人"的直接回应。据微某信团队约 2014 年前后的公开灰度数据,约七成用户能达到心跳上限——历史数据,仅供参考。
维度 | 详情 |
|---|---|
优势 | "失败累积才判死"直接压住单次心跳的假阳性;延迟测试五次剔除偶发抖动;新连接先短心跳验证再放宽 |
代价 | 判死被刻意延后,真死连接多滞留几个周期;累积逻辑全压在客户端,纯服务端无法复刻 |
发现死连接只是上半场,下半场是重连——若不加节制,网络一恢复海量客户端同时回连,会把接入层瞬间打垮。退避重连在融某云、网某某信等多家 SDK 文档中均有记载,其中融某云给了明确的倍增曲线:首次断开 1 秒后重连,失败则间隔翻倍(1s → 2s → 4s…),重试 N 次仍失败就停手,只在网络变化或重开应用时再试。
维度 | 详情 |
|---|---|
优势 | 倍增退避避免网络恢复瞬间的"重连风暴"打垮接入层;SDK 封装让判活 / 重连对接入方透明 |
代价 | 退避越久,用户端"恢复在线"越慢;SDK 把策略封死,参数对使用方是黑盒,排障拿不到细节 |
对于超时太短会误杀、太长又滞留——有个很值的折中:两段式判死(二次确认)。读超时第一次触发时先别急着断,连接很可能只是抖了一下;服务端主动补发一个心跳探测包、再等一个较短的窗口(比如几十秒),二次窗口内还收不到回应才真正判死清理。
这个是实打实的收益:滤掉绝大多数偶发抖动造成的误判,又不像"一味调大超时"那样让真死连接长期滞留——真死的补探也不会回,只多等一个短窗口就被清掉。
就是别把判活赌在单一信号——让多路信号一起投票。
同时观察几路信号:心跳空闲时长(主信号)、下行写操作是否持续失败、TCP 层抛出的异常(连接 reset、写错误)。单独哪一路都不够靠谱,组合起来能互相补盲:写操作连续失败比心跳超时更早暴露问题,TCP reset 就是确凿的断开证据。原则上——确凿信号(如 TCP reset)立即判死,模糊信号(如单次心跳超时)走二次确认,按确定性分级比一刀切等心跳窗口灵敏多了。
判死会触发一连串清理——移除本机连接注册、改全局在线状态、通知路由层。坑在于:判死、重连、新连接建立可能并发发生。清理逻辑不幂等,就容易出现"旧连接的判死清理误删了同一用户刚建好的新连接在线状态"这类竞态,把刚上线的用户又改回离线。
防御解决也朴素:清理时带上连接唯一标识(建连时间戳或会话 ID),只清理"确实是我这条连接"的状态,已被新连接覆盖就跳过。否则前面发现得再准,最后一步翻车照样制造状态错乱。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。