首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >带redis +集群的Node.js + Socket.IO缩放

带redis +集群的Node.js + Socket.IO缩放
EN

Stack Overflow用户
提问于 2015-11-11 17:20:12
回答 1查看 8.5K关注 0票数 12

目前,我面临的任务是必须使用AmazonEC2扩展Node.js应用程序。据我所知,这样做的方法是让每个子服务器使用使用集群的所有可用进程,并具有粘性连接,以确保每个连接到服务器的用户都“记住”他们以前的会话中的数据是什么工作人员。

完成此操作后,我所知道的下一个最佳步骤是根据需要部署尽可能多的服务器,并使用nginx在所有服务器之间负载平衡,同样使用粘性连接来知道每个用户数据在哪个“子”服务器上。

因此,当用户连接到服务器时,会发生这样的情况吗?

客户端连接->查找/选择服务器->查找/选择进程-> Socket.IO握手/连接等。

如果没有,请允许我更好地理解这个负载平衡任务。我也不明白红皮书在这种情况下的重要性。

下面是用于将一台计算机上的所有CPU用于单独的Node.js进程的代码:

代码语言:javascript
复制
var express = require('express');
cluster = require('cluster'),
net = require('net'),
sio = require('socket.io'),
sio_redis = require('socket.io-redis');

var port = 3502,
num_processes = require('os').cpus().length;

if (cluster.isMaster) {
// This stores our workers. We need to keep them to be able to reference
// them based on source IP address. It's also useful for auto-restart,
// for example.
var workers = [];

// Helper function for spawning worker at index 'i'.
var spawn = function(i) {
    workers[i] = cluster.fork();

    // Optional: Restart worker on exit
    workers[i].on('exit', function(worker, code, signal) {
        console.log('respawning worker', i);
        spawn(i);
    });
};

// Spawn workers.
for (var i = 0; i < num_processes; i++) {
    spawn(i);
}

// Helper function for getting a worker index based on IP address.
// This is a hot path so it should be really fast. The way it works
// is by converting the IP address to a number by removing the dots,
// then compressing it to the number of slots we have.
//
// Compared against "real" hashing (from the sticky-session code) and
// "real" IP number conversion, this function is on par in terms of
// worker index distribution only much faster.
var worker_index = function(ip, len) {
    var s = '';
    for (var i = 0, _len = ip.length; i < _len; i++) {
        if (ip[i] !== '.') {
            s += ip[i];
        }
    }

    return Number(s) % len;
};

// Create the outside facing server listening on our port.
var server = net.createServer({ pauseOnConnect: true }, function(connection) {
    // We received a connection and need to pass it to the appropriate
    // worker. Get the worker for this connection's source IP and pass
    // it the connection.
    var worker = workers[worker_index(connection.remoteAddress, num_processes)];
    worker.send('sticky-session:connection', connection);
}).listen(port);
} else {
// Note we don't use a port here because the master listens on it for us.
var app = new express();

// Here you might use middleware, attach routes, etc.

// Don't expose our internal server to the outside.
var server = app.listen(0, 'localhost'),
    io = sio(server);

// Tell Socket.IO to use the redis adapter. By default, the redis
// server is assumed to be on localhost:6379. You don't have to
// specify them explicitly unless you want to change them.
io.adapter(sio_redis({ host: 'localhost', port: 6379 }));

// Here you might use Socket.IO middleware for authorization etc.

console.log("Listening");
// Listen to messages sent from the master. Ignore everything else.
process.on('message', function(message, connection) {
    if (message !== 'sticky-session:connection') {
        return;
    }

    // Emulate a connection event on the server by emitting the
    // event with the connection the master sent us.
    server.emit('connection', connection);

    connection.resume();
});
}
EN

回答 1

Stack Overflow用户

发布于 2016-01-15 07:29:27

我相信你的一般理解是正确的,尽管我想说几句:

负载平衡

您正确地认为,实现负载平衡的一种方法是在不同实例之间实现nginx负载平衡,并且在每个实例中在它创建的辅助进程之间具有集群平衡。然而,这只是一种方式,并不一定总是最好的方法。

实例之间

首先,如果您无论如何都在使用AWS,您可能需要考虑使用ELB。它是专门为负载平衡EC2实例设计的,它使得在实例之间配置负载平衡的问题变得简单。它还提供了许多有用的特性,并且(使用自动缩放)可以使扩展变得非常动态,而不需要您做任何努力。

ELB所具有的一个特性(与您的问题特别相关)是,它支持粘性会话从盒子里出来 --只需标记复选框即可。

但是,我必须补充一个重要的警告,即电子束可以破坏socket.io中的奇形怪状。如果您只使用长轮询,您应该会很好(假设启用了粘性会话),但实际的websockets工作在非常令人沮丧和不可能之间。

过程间

虽然除了使用集群( Node和不带 )之外还有很多替代方法,但我倾向于同意集群本身通常是非常好的。

然而,它不起作用的一种情况是,当您希望负载均衡器后面有粘性会话时,就像您在这里所做的那样。

首先,应该明确指出,您甚至需要粘稠会话的唯一原因首先是因为socket.io依赖于存储在请求之间的会话数据(在websockets握手期间,或者基本上是在长轮询过程中)。通常,由于各种原因,应该尽可能避免依赖以这种方式存储的数据,但是对于socket.io,您没有选择的余地。

现在,这似乎还不算太糟,因为集群可以使用Socket.IO的文档中提到的粘性会话模块或您似乎正在使用的片段来支持粘性会话。

问题是,由于这些粘性会话基于客户端的IP,因此它们不会在负载均衡器(无论是nginx、ELB或其他任何东西)后面工作,因为在这个点上,实例中可见的所有内容都是负载均衡器的IP。您的代码试图散列的remoteAddress实际上不是客户机的地址。

也就是说,当节点代码试图充当进程之间的负载均衡器时,它试图使用的IP始终是其他负载均衡器的IP,在实例之间保持平衡。因此,所有请求都将在同一个进程中结束,从而破坏集群的整个目的。

在这个问题中,您可以看到这个问题的细节,以及解决这个问题的几种可能的方法(其中没有一个特别漂亮)。

Redis的重要性

如前所述,一旦有多个实例/进程接收用户的请求,会话数据的内存存储就不再足够了。粘性会话是一种方法,尽管还有其他更好的解决方案,其中包括Redis可以提供的中央会话存储。有关该主题的相当全面的回顾,请参见此帖子

不过,考虑到您的问题是关于socket.io的,我想您可能是指Redis对websockets的特殊重要性,所以:

当您有多个socket.io服务器(实例/进程)时,给定的用户在任何给定的时间只会连接到一个这样的服务器。但是,任何服务器在任何时候都可能希望向给定用户发出消息,甚至向所有用户发送广播,而不管它们当前在哪个服务器下。

为此,socket.io支持“适配器”( Redis就是其中之一),允许不同的socket.io服务器之间进行通信。当一台服务器发出一条消息时,它进入Redis,然后所有服务器都会看到它(Pub/Sub)并将其发送给他们的用户,确保消息将到达其目标。

在Socket.IO的关于多个节点的文档中同样解释了这一点,在这个堆栈溢出回答中可能更好。

票数 32
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/33656128

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档