我有一个NodeJS TCP服务器,它将每个套接字连接存储在一个数组中,并附带一些我需要与之关联的misc信息:
var clients = [];
net.createServer(sslOptions, function (_socket) {
_socket.name = 'someName';
_sockect.foo = 'bar';
clients.push(_socket);
}
服务器经常为某些业务逻辑内容迭代数组 clients 。有时一个条件触发,我需要断开其中一个插座:
if(condition) {
for(let x = 0; x < clients.length; x++) {
if(clients[x].name == 'someName') {
clients[x].disconnect();
clients.splice(x, 1);
return;
}
}
}
有两件事让我担心这种方法。如果我决定使用PM2 clusters或任何其他Nodejs Process Manager复制我的应用程序进行负载平衡,那么不同的进程如何共享相同的数组 clients ?如果一个进程在迭代数组而其他进程正在删除一个索引时会发生什么,可能会出现竞争条件,最终会导致错误的断开和删除。
当然,解决方案是使用类似原子数据库的东西,但我不知道如何在Mongo或Redis之类的东西中存储套接字。
答案 0 :(得分:2)
不同进程如何共享相同的阵列客户端?
他们没有。
两个node.js进程无法访问同一个数组(它们位于不同的进程和单独的Javascript解释器中),因此在这方面没有直接数据共享或相应竞争条件的机会。
如果一个进程在迭代数组而其他进程正在删除一个索引时会发生什么,可能会出现最终导致错误断开和删除的竞争条件。
因为Javascript只在一个线程中运行所有Javascript,所以你不能同时尝试修改同一个数组的多个Javascript。如果您正在迭代一个数组并进行异步调用,那么您需要做一些安全事情,因为在等待异步响应时阵列可能会发生变化,但在同步for
循环时它不会发生变化以运行为例。
要了解如何制作类似于使用多个node.js进程的内容,您可以从socket.io服务器如何支持群集中学到很多东西,因为它有相同的基本问题需要解决。他们希望能够发送到所有连接的套接字(跨所有集群服务器)或任何特定连接(无论消息来自哪个服务器以及连接的客户端实际连接到哪个服务器)。
对于socket.io,它们基本上使用所有集群进程都可以访问的公共中间存储(在它们的情况下是一个redis数据存储)。由于redis是为多用户访问而设计的,因此客户可以仔细使用其API并避免竞争条件。然后,redis存储用于存储关于每个连接用户的元数据以及哪个服务器进程包含其当前连接的指示符。
要从任何群集服务器进程向特定用户发送消息,请从redis存储中获取该用户的数据。如果用户连接到本地服务器进程(您正在进行查找的进程),那么您可以直接从元数据中获取其ID,并在您自己的本地连接客户端列表中查找它们发送到他们的插座。
如果它们连接到不同的服务器,则会向该服务器发送一条消息,要求它们将消息中继到特定的套接字ID值。当该服务器收到该消息时,它会在其自身的连接到其进程的客户端列表中查找该ID,获取其套接字并向其发送消息。
当客户端与任何群集进程连接或断开连接时,会在redis存储中添加或删除连接。请记住,您不能(并且不能)在redis存储中存储实际的套接字对象,因为套接字对象是特定进程的本地对象。您只在redis存储中存储元数据和服务器ID,因此任何查询redis存储的人都可以确定哪些用户已连接以及他们当前连接到哪个服务器。您通常会使用唯一的用户名或其他唯一ID来表示每个用户,类似的服务器将由某种ID(可能是主机/端口号)表示,允许您连接到它们。
为了避免redis存储中的竞争条件,您只需要在修改数据时使用良好的多用户数据管理实践和正确的redis API,并避免大多数问题。关于竞争条件的进一步建议将需要更多关于您尝试修改的细节。但是,大多数情况下,您只是在连接时添加连接用户,在断开连接时删除已连接的用户,并且在使用正确的API时这些都是原子操作。如果您正在获取用户列表然后对其进行操作,那么如果他们在您查询它们的时间与您尝试实际发送给它们之间断开连接,则会有潜在的竞争条件。为此,当您尝试发送错误消息时,您必须准备妥善处理错误。
每个单独的node.js进程都会维护自己的套接字数组,这些套接字连接到自己的进程。由于node.js将JS作为单线程运行,并且该数组不与任何其他进程或线程共享,因此如果您的代码编写正确,则只有在访问或维护该数组时才有竞争条件。
当然,解决方案是使用类似原子数据库的东西,但我不知道如何在Mongo或Redis之类的东西中存储套接字。
您不会在这些数据库中存储套接字。您存储套接字ID值和服务器ID值。服务器ID值(可以是主机/端口字符串)可用于连接到具有该套接字ID连接的正确服务器。然后,接收服务器可以使用套接字ID在其自己的数组中查找实际的套接字对象以获取其自己的连接。
答案 1 :(得分:0)
我认为您必须考虑使用Socket.io Redis代替。 他们将客户存储在RedisAdapter中。您可以通过以下方式获取客户ID列表:
io.of('/').adapter.clients((err, clients) => {
console.log(clients); // an array containing all connected socket ids
});
io.of('/').adapter.clients(['room1', 'room2'], (err, clients) => {
console.log(clients);
});
// you can also use
io.in('room3').clients((err, clients) => {
console.log(clients); // an array containing socket ids in 'room3'
});