Nodejs socket.io 当大量客户端同时继开时会存在没有回收客户端信息的问题

Nodejs socket.io 当大量客户端同时继开时会存在没有回收客户端信息的问题

还是用socket.io写消息中心的问题…压测的时候如果出现当大量客户端同时继开时socket.io中会存在没有回收客户端信息且一直没法回收,这时如果在不断推消息给socket.io时会造成cpu和内存升高一直到OM…。 消息推送模式: service -> redis -> socket.io ->客户端 集群:用的nginx

这个问题我想是当存在废客户端信息的时候…socket.io在缓存发送不出去的消息且不断循环尝试造成的,所以应该想办法回收这些废客户端来解决…可我一直没搞定…晕了…求帮助…

另求:怎么得到当前进程所在cpu的负载情况…我想做个流量控制当cpu高的情况下不接收redis中的消息


6 回复

Node.js Socket.io 当大量客户端同时继开时会存在没有回收客户端信息的问题

在使用 Socket.io 进行实时通信时,特别是在高并发场景下,可能会遇到客户端断开连接后未能及时释放资源的情况。这会导致内存泄漏,进而导致 CPU 和内存使用率持续上升。

问题描述

假设我们有一个消息推送系统,架构如下:

  • service 发送消息到 Redis
  • Redis 将消息广播到 Socket.io
  • Socket.io 将消息推送给客户端

当大量客户端同时断开连接时,Socket.io 可能无法及时回收这些断开的客户端信息,导致内存泄漏。这种情况下,如果继续向 Socket.io 推送消息,会导致 CPU 和内存使用率上升,最终可能导致系统崩溃(OM)。

解决方案

  1. 监听客户端断开事件:通过监听客户端的 disconnect 事件,可以在客户端断开连接时清理相关资源。
  2. 定时清理无效连接:可以定期检查并清理无效的客户端连接。
  3. 监控 CPU 负载:通过监控 CPU 负载,可以在负载较高时减少或暂停消息处理。

示例代码

const io = require('socket.io')(server, {
  transports: ['polling', 'websocket'],
});

// 监听客户端断开事件
io.on('connection', (socket) => {
  console.log('Client connected:', socket.id);

  socket.on('disconnect', () => {
    console.log('Client disconnected:', socket.id);
    // 清理与该客户端相关的资源
    // 例如:删除存储的用户信息、清理队列等
  });
});

// 定时清理无效连接
setInterval(() => {
  const sockets = io.sockets.connected;
  Object.keys(sockets).forEach((socketId) => {
    const socket = sockets[socketId];
    if (!socket.client.conn.writable) {
      console.log('Cleaning up invalid socket:', socketId);
      socket.disconnect();
    }
  });
}, 5000); // 每5秒检查一次

// 获取当前进程的 CPU 使用率
const os = require('os');
const cpuUsage = os.loadavg()[0]; // 返回过去1分钟内的平均负载
console.log(`Current CPU load: ${cpuUsage}`);

// 根据 CPU 负载决定是否接收新消息
if (cpuUsage > 0.8) { // 假设阈值为0.8
  console.log('CPU load is high, not accepting new messages.');
} else {
  // 正常处理消息
}

解释

  1. 监听客户端断开事件

    • socket.on('disconnect', ...):当客户端断开连接时触发此事件,可以在这里清理与该客户端相关的资源。
  2. 定时清理无效连接

    • setInterval(...):每5秒检查一次所有连接的客户端,如果发现客户端连接不可写,则认为该连接无效并断开。
  3. 获取 CPU 负载

    • os.loadavg()[0]:返回过去1分钟内的平均负载,可以用于监控 CPU 使用率,并据此决定是否接受新的消息。

通过以上措施,可以有效避免因大量客户端同时断开连接而导致的内存泄漏问题,同时可以根据 CPU 负载动态调整消息处理策略。


  • socket.io应该是有心跳机制检测的,你怎么压测的?
  • 我以前遇到个问题是redis库不能及时处理大量请求导致node进程内存飙升至OM

2 机器 6个核 6进程

3000客户端 每50毫秒向3000客户端推送消息

昨天我进一步测试当使用volatile消息的时候服务会稳定

我计算了一下:

每秒我会推出20次消息也就是每秒 6W消息(20*3000) 客户端统计每秒收到 5W多一点

也就是说我这2机器6进程达不到 每秒6W,这个时socket.io开始排队缓存消息最后越来越多照成服务异常,我是这么理解的

对于这个问题我准备对推入的消息做分类就是区分是否为volatile消息,然后对nodejs 接收消息做流量控制

当使用 Socket.IO 处理大量客户端连接时,确实可能会遇到内存泄漏或未及时回收已断开连接的客户端信息的问题。这主要是由于 Socket.IO 客户端连接管理机制导致的。为了有效处理这个问题,你可以采取以下几种方法:

  1. 强制清理未关闭的连接:确保每个客户端在断开连接时正确地调用了 socket.disconnect() 方法。

  2. 使用心跳检测机制:通过设置较短的心跳间隔来检测并及时关闭无效的连接。

  3. 手动清理:周期性地检查并手动清理已断开但未被 Socket.IO 正确处理的连接。

示例代码:

const io = require('socket.io')(server, {
    pingTimeout: 5000, // 心跳超时时间
    pingInterval: 3000, // 心跳间隔时间
});

io.on('connection', (socket) => {
    console.log('A user connected');

    socket.on('disconnect', () => {
        console.log('User disconnected');
        // 在这里可以执行额外的清理操作
    });

    // 发送消息
    socket.on('send message', (data) => {
        // 处理消息
    });
});

// 手动清理未关闭的连接(周期性任务)
setInterval(() => {
    const sockets = io.sockets.sockets;
    for (let id in sockets) {
        let socket = sockets[id];
        if (socket.client.conn.readyState !== 1) {
            socket.disconnect();
        }
    }
}, 60000); // 每分钟检查一次

关于获取 CPU 负载情况,可以使用 os 模块提供的方法:

const os = require('os');
console.log(os.loadavg()); // 获取 CPU 负载情况

根据 CPU 负载情况,你可以动态调整接收消息的行为,例如减少消息处理频率或暂时停止接收消息。

回到顶部