我正在开发一个使用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]。发射"效率不高。示例如下:
socket.on('chat message', function(msg){
io.emit('chat message', msg);
});
VS
for (i = 0; i < relevant_sockets.length; i++) {
io.sockets.connected[relevant_sockets[i]].emit('chat message', msg);
}
2)由于我使用多台服务器,套接字ID是否保证是唯一的?我希望redis模块能够处理这个问题,因为它的全部目的是链接进程和服务器,但我只想确定。
提前感谢您提供任何帮助。
答案 0 :(得分:0)
1)你的建议很常见。您将节省一点内存,但最大的收益将是负载下的CPU和响应时间。
2)分配给每个套接字的id看起来是一个随机生成的id。看一下socket.io-redis的源代码,似乎没有任何东西可以确保每个id都是唯一的。如果您担心ID不是唯一的,您可以生成自己的唯一ID并将其存储在套接字ID旁边。然后你只需使用两个id作为查找的键(甚至可以创建一个哈希值)。
答案 1 :(得分:0)
当你需要将消息发送给所有客户时,你肯定会不想要向所有客户端广播消息。您的方法#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的独特性而言,我从未遇到过这个问题。 Google以大规模实施了类似的架构,并没有遇到问题。你可以阅读它here。
我希望这会有所帮助;总的来说你的建筑非常好!