【问题标题】:Socket IO scaling: is it more efficient to send messages to specific socket id's?套接字 IO 缩放:将消息发送到特定套接字 id 是否更有效?
【发布时间】:2015-12-30 03:58:46
【问题描述】:

我正在使用需要跨多个服务器扩展的 Socket IO 开发应用程序。这是通过 socket.io-redis 实现的,它本质上将我所有运行套接字 IO 的服务器互连,并将 Redis 视为一个数据存储,我的所有运行 Socket IO 的服务器/节点都从中发布消息。

为了获得更好的上下文,下面是 MSDN 中概述我的设置的图表:

这是我的问题:

如果客户端 A 发送一条仅供客户端 C 接收的消息,我觉得向客户端 B 和 D 发布消息是浪费带宽。我的想法是,我会在服务器端记录用户 ID以及它们相应的打开套接字 id。然后,当客户端发送消息时,我会进行查找并找到用户的套接字 ID 并发布消息。这似乎很好,很花哨,并且严重依赖于强大的系统来保持用户 ID 和套接字 ID 的表是最新的,但是我有两个关于实现这个系统的问题:

1) 仅将消息发送到相关的套接字 id,而不是发送到每个服务器的整个通道是否更节省内存?我意识到总共有 4 个客户端,这个问题非常小,但想象一下,如果我在 2 台服务器上有 2000 个并发用户。如果 1 个客户端发送一条针对 2 个客户端的消息,我想向 1997 年的其他客户端发送消息比仅发送给这 2 个套接字 ID 更费力,除非“io.sockets.connected[socket_id].emit”效率不高。示例如下:

socket.on('chat message', function(msg){
    io.emit('chat message', msg);
});

对比

for (i = 0; i < relevant_sockets.length; i++) { 
    io.sockets.connected[relevant_sockets[i]].emit('chat message', msg);
}

2) 由于我使用多台服务器,socket id 是否保证是唯一的?我希望 redis 模块能够处理这个问题,因为它的全部目的是链接进程和服务器,但我只是想确定一下。

提前感谢您的帮助。

【问题讨论】:

    标签: node.js sockets socket.io


    【解决方案1】:

    1)您的建议是很常见的做法。您将节省一点内存,但最大的收获将是 CPU 和负载下的响应时间。

    2) 分配给每个套接字的 id 看起来是一个随机生成的 id。查看 socket.io-redis 的源代码,似乎没有任何东西可以确保每个 id 都是唯一的。如果您担心 id 不是唯一的,您可以生成自己的唯一 id 并将其存储在套接字 id 旁边。然后,您只需使用这两个 id 作为查找的键(甚至可以从它们中创建一个散列)。

    【讨论】:

    • 感谢您提供的重要信息。我对唯一套接字 ID 的主要担忧是关于安全性。如果两个客户端可能具有相同的套接字 ID,我如何确保不会向错误的客户端发送不适合他们的消息?
    • 我的意思是,这真的没有神奇的解决方案或技巧。您可以信任随机生成器而不必担心 ID 冲突的可能性,或者您可以编写一个例程来验证每个 id 实际上是唯一的。总的来说,这是一个非常广泛的计算主题。许多大型复杂系统依赖于随机生成的 UUID,但理论上可能存在冲突。如果我是你,我不会担心,相信唯一的 ID 生成器。
    【解决方案2】:

    当您只需要将消息发送给一个客户端时,您肯定不希望将消息广播给所有客户端。您的方法 #1 就像在“上帝模式”下操作,可能只针对管理员。

    我已经实现了与您的架构类似的架构。但是,我也使用 Redis 将我的客户端 ID(客户端名称)映射到套接字 ID,反之亦然。例如,您可以使用套接字 id 123 作为“omegalen”加入。然后由于某种原因断开连接,您会得到一个新的套接字 id:456。当我需要向“omegalen”发送消息时,我需要先在 Redis 中查找“omegalen”并获取您最近的套接字 id:456,因为 123 已经消失了,它将无法正常工作。这是一些示例代码,可以提供一个想法。请注意,我在密钥前面加上了命名空间,并且还为它们设置了过期时间(24 小时):

    var client = redis.createClient(redisPort, redisHost);
    
    function _updateSocketId(clientName, socketId) {
        client.set("client/" + clientName, socketId);
        client.set("socket/" + socketId, clientName);
    
        client.expire("client/" + clientName, 1 * 24 * 60 * 60); //in seconds. 1 day
        client.expire("socket/" + socketId, 1 * 24 * 60 * 60); //in seconds. 1 day
    }
    
    function _getSocketId(clientName, callback) {
        //all clients are stored and prefixed with the namespace below
        client.get("client/" + clientName, function(err, data) {
            if(err) {
                callback("Error retrieving key for " + clientName + "Error: " + err, null);
                return;
            }
            var socketId = data;
            logger.debug("Retrieved socket for client/" + clientName + ". Socket id: " + socketId);
            callback(null, socketId);
    
        });
    }
    
    function _findClientBySocket(socketId) {
        var name = client.get("socket/" + socketId);
        return name;
    }
    
    function _deleteSocketId(socketId, clientName) {
        logger.debug("deleting socket: " + socketId + " - clientName: " + clientName);
        client.del("socket/" + socketId);
        client.del("client/" + clientName);
    }
    
    module.exports.getSocketId = _getSocketId;
    

    然后,在设置我的 websocket 服务器时,我会监听事件并相应地更新客户端/套接字 id 映射:

    socket.on("join", function (clientName, callback) {
        //update Redis with new client id / socket id
        redisUtil.updateSocketId(clientName, socket.client.id);
    })
    
    socket.on("rejoin", function (clientName) {
        redisUtil.updateSocketId(clientName, socket.client.id);
    })
    
    socket.on("disconnect", function (clientName) {
        redisUtil.deleteSocketId(socket.client.id, clientName);
    })
    

    上面的代码只是一个示例代码,可以让您了解一下,因为它还可以很容易地增强为每个客户端处理多个套接字(假设您打开了两个浏览器客户端)。你的架构图很好,它会工作,但你只需要向目标客户发送消息。

    我不想跑题,但您也可以在 socket.io 中使用命名空间并向一组用户发出事件,例如,只向一种特殊类型的客户端发送事件:

     var ssClients = io.of("/ssClients");  //socket.io namespace for the some ss clients
    ssClients.on("connection", function (socket) {
    ..
    

    这不是您的问题的一部分,但我认为如果您需要按命名空间广播/过滤消息,值得一提。

    就 socket.io id 的唯一性而言,我从来没有遇到过这个问题。谷歌大规模地实施了类似的架构,他们没有遇到问题。您可以阅读它here。

    我希望这会有所帮助;总体而言,您的架构非常好!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-21
      相关资源
      最近更新 更多