WebSocket重连代码引发资源占用过高的原因及调试方案
WebSocket闲置后CPU/内存暴涨问题分析与解决
代码中的明显问题
- 未清理旧WebSocket实例导致内存泄漏:每次重连时直接覆盖全局
SOCKET变量,但旧的WebSocket实例及其绑定的事件监听器仍会被浏览器持有,无法被垃圾回收。多次休眠唤醒后,会堆积大量无效的socket实例,直接导致内存占用飙升。 - 固定间隔重连无退避机制:socket断开后每隔5秒强制重连,若服务器持续无法连接,会不断创建新的socket连接,加剧内存消耗,甚至触发服务器限流后进入恶性循环。
- setInterval未适配页面休眠状态:电脑休眠时浏览器定时器会暂停,唤醒后可能一次性触发所有堆积的定时器回调,短时间内大量执行日志输出或socket请求,引发CPU占用过高。
调试步骤
- 内存泄漏排查:打开Firefox开发者工具「内存」面板,分别在页面刚加载、闲置1小时、唤醒后各拍一次内存快照,对比
WebSocket对象的数量,若数量持续增长即可确认实例堆积问题。 - CPU占用定位:在CPU飙升时,用「性能」面板录制30秒左右的性能数据,查看调用栈中哪个函数(如socket事件回调、定时器函数)占用了大量CPU时间。
- 重连次数监控:在
startSocket函数开头添加console.log('重连次数:', ++reconnectCount)(需先声明全局reconnectCount=0),观察是否存在短时间内大量重连的情况。 - 页面可见性测试:添加页面可见性监听,打印定时器触发时机,确认是否在唤醒后集中执行大量回调。
修复方案
1. 清理旧Socket实例+优化重连逻辑
每次重连前主动关闭旧socket并解除事件绑定,同时添加指数退避重连机制:
var SOCKET = null; var reconnectCount = 0; var heartbeatTimer = null; function startSocket() { // 清理旧实例 if (SOCKET) { SOCKET.removeEventListener('open', onSocketOpen); SOCKET.removeEventListener('close', onSocketClose); SOCKET.removeEventListener('error', onSocketError); if (SOCKET.readyState !== WebSocket.CLOSED) { SOCKET.close(1000, '主动清理旧连接'); } SOCKET = null; } SOCKET = new WebSocket('wss://[REDACTED]'); SOCKET.addEventListener('open', onSocketOpen); SOCKET.addEventListener('close', onSocketClose); SOCKET.addEventListener('error', onSocketError); } // 独立事件回调,方便移除监听 function onSocketOpen(event) { document.getElementById('disconnected-overlay').style.display = 'none'; reconnectCount = 0; scheduleHeartbeat(); } function onSocketClose(event) { console.log(`websocket closed: ${event.code} (${event.reason})`); document.getElementById('disconnected-overlay').style.display = 'block'; // 指数退避:第一次5秒,第二次10秒,最多60秒 const delay = Math.min(5000 * Math.pow(2, reconnectCount), 60000); setTimeout(startSocket, delay); reconnectCount++; clearHeartbeat(); } function onSocketError(event) { console.log(`websocket error: ${event}`); document.getElementById('disconnected-overlay').style.display = 'block'; const delay = Math.min(5000 * Math.pow(2, reconnectCount), 60000); setTimeout(startSocket, delay); reconnectCount++; clearHeartbeat(); } // 用setTimeout替代setInterval,避免定时器堆积 function scheduleHeartbeat() { heartbeatTimer = setTimeout(() => { console.log("4 minutes"); if (SOCKET?.readyState === WebSocket.OPEN) { SOCKET.send(JSON.stringify({ "user_name": "debugging", "change": { "AddMinutes": 0 }, })); // 仅在socket正常时继续调度下一次心跳 scheduleHeartbeat(); } else { console.log("socket was not open"); } }, 240000); } function clearHeartbeat() { if (heartbeatTimer) { clearTimeout(heartbeatTimer); heartbeatTimer = null; } } function mainSetup() { startSocket(); // 监听页面可见性,休眠时暂停心跳 document.addEventListener('visibilitychange', () => { if (document.hidden) { clearHeartbeat(); } else { if (SOCKET?.readyState === WebSocket.OPEN) { scheduleHeartbeat(); } } }); }
2. 适配页面休眠状态
通过visibilitychange事件监听页面是否可见,休眠时暂停心跳定时器,唤醒后根据socket状态恢复,避免唤醒后批量执行回调。
内容的提问来源于stack exchange,提问作者Violet
相关产品推荐
相关产品推荐

