已采纳
回答正文
回复
讨论正文
该问题的正文与回答。
背景
React 18 + TS 5 的 B 端实时协作中台(光标同步 + 在线状态 + 增量 diff 推送),线上约 5000 并发连接,平均在线 45min。现有链路:前端 setInterval(15s) 主动发 ping,服务端回 pong;前端 3 次未收到 pong 判定断开,指数退避重连(1s / 2s / 4s,上限 30s)。#414 那篇 3 万连接雪崩复盘里的 5 组参数我们基本照着抄了。
现象(卡点)
ws.OPEN 状态还挂着——我们的「在线用户数」和「协作房间成员列表」里这些人都还在,光标偶尔还会闪一下(其实是旧帧被 flush)。客服工单里高频出现「我早退了他还显示在线」「我切了网,对方还看到我光标」。beforeunload 在浏览器崩溃 / 合盖时根本不触发,visibilitychange 只能覆盖「切后台」不能覆盖「进程死亡」。已尝试的排查与结果
server-side ping/pong(30s 间隔 + 1 次超时即断):能清理大部分半开,但弱网误伤明显(灰度数据:误断率从 0.3% 升到 1.1%,集中在 4G / 海外)。visibilitychange 里主动发一次 ping + 收 pong 后再决定「真断 / 假断」:只能覆盖「切后台」,覆盖不了「进程死亡 / 网络分区」。onclose + onerror + visibilitychange 三类事件到 RUM(#770 问的建模方向):能看见「事件」,但服务端没法据此主动清理半开连接,因为半开的特征是前端根本不发任何事件。TCP keepalive 默认值(#592 / #1451 都提到),但服务端是 Node.js 的 ws 库,socket.setKeepAlive(true, 60000) 默认 60s 探测,调短到 10s 担心内核层探测包在 NAT 后面照样黑洞,收益存疑。期望得到的答案形态
server-side ping/pong + TCP keepalive + 应用层心跳,这三层各自承担什么职责?有没有「只靠应用层心跳 + RTT 自适应」就能覆盖 90% 场景、不用动内核参数的做法?beforeunload / visibilitychange / Page Visibility API / navigator.onLine 各自覆盖哪类「假死」?有没有「同一用户多 tab 只保留一条活跃 WS」的去重方案(服务端按 userId + token 踢旧连接?前端用 BroadcastChannel 协商?)?边界条件
ws 库 / socket.io 二选一,社区两种方案都欢迎),不是 Go / Java,TCP keepalive 的调节粒度受 Node 事件循环影响;引用
setKeepAlive 的实际收益没验证过。