WebSocket 连接管理有哪些关键实践?断线重连怎么做?
WebSocket 连接管理围绕生命周期展开:建立、保活、重连、清理。建立时客户端发起连接,服务端做鉴权和元数据登记(用户 ID、设备信息),然后双方进入通信状态。保活用心跳机制——客户端定时发 ping,服务端回 pong,超时未收到 pong 则认为连接已死,这是检测"半开连接"(对端已断开但本端不知道)的唯一可靠手段。重连是客户端的职责,核心策略是指数退避:首次 1 秒后重试,之后 2s、4s、8s,上限 30s,避免服务端刚恢复就被雪崩式重连打垮。加随机抖动(jitter)防止大量客户端同时重连。清理是关闭连接后释放定时器、事件监听器、内存中的消息队列等资源,避免泄漏。
追问
指数退避为什么要加随机抖动?
如果 1000 个客户端同时断线,都在 1s、2s、4s 后重连,会出现"惊群效应"——服务端刚恢复就被第一波重连打垮,然后再次宕机,下一波重连又打垮,形成恶性循环。加 jitter(如 delay = baseDelay * 2^retry + Math.random() * 1000)让各客户端的重连时间错开,将瞬间冲击分散到时间窗口内。AWS 和 Google 的重连库都默认启用 jitter。
心跳间隔设多少合适?太短和太长各有什么问题?
通常 30 秒。太短(如 5 秒)会浪费带宽和服务端资源,10 万连接每 5 秒 ping 一次就是 2 万次/秒;太长(如 120 秒)无法及时发现半开连接,用户可能等 2 分钟才知道消息收不到了。移动端建议 30-45 秒,服务端 idle timeout 设为心跳间隔的 2-3 倍(如 90 秒),给网络波动留余量。Nginx 代理 WebSocket 时也要配置 proxy_read_timeout 与之一致。
连接池在什么场景下需要?单连接不够用吗?
浏览器对同一域名的 WebSocket 连接数没有硬限制(不像 HTTP/1.1 的 6 个),所以大多数场景单连接就够了,在应用层做消息多路复用(通过 type 字段区分业务)。需要连接池的场景:不同业务域隔离(聊天和行情走不同连接,避免互相阻塞);大规模数据传输(单连接带宽不够时分流);高可用(主连接断开时备用连接无缝切换)。不要为了"看起来专业"而引入连接池,多连接意味着多倍的心跳和内存开销。
页面切换或组件卸载时 WebSocket 连接怎么处理?
SPA 路由切换时不要断开重连,把 WebSocket 实例提升到全局(Redux store 或 Context),路由组件只订阅/取消订阅消息,连接本身持续存在。组件卸载时必须移除事件监听器,否则闭包引用的旧 state 会导致内存泄漏和僵尸回调。页面真正关闭(beforeunload)时发关闭帧优雅断开。React 项目推荐用自定义 Hook 封装,useEffect 的 cleanup 中移除 listener。
javascript// 指数退避 + 随机抖动重连 function createReconnectingWebSocket(url) { let ws; let retryCount = 0; const maxRetries = 10; const baseDelay = 1000; const maxDelay = 30000; function connect() { ws = new WebSocket(url); ws.onopen = () => { retryCount = 0; // 连接成功,重置计数 }; ws.onclose = (event) => { if (event.code === 1000 || retryCount >= maxRetries) return; const delay = Math.min(baseDelay * Math.pow(2, retryCount), maxDelay) + Math.random() * 1000; // 随机抖动 retryCount++; setTimeout(connect, delay); }; } connect(); return ws; } // 心跳机制 function startHeartbeat(ws, interval = 30000) { const timer = setInterval(() => { if (ws.readyState === WebSocket.OPEN) { ws.ping(); // 或 ws.send(JSON.stringify({ type: 'ping' })) } }, interval); return timer; // 组件卸载时 clearInterval(timer) }