Node.js 集群架构:如何扩展 master worker
Node.js Cluster architecture: how to scale master worker
我已经构建了一个 Node.js 内置 cluster 架构和 master/worker 配置。该应用程序使用 express
来提供 api 和静态文件,并使用 Docker:
进行部署
[D O C K E R: 8080] --- N ---> [W O R K E R: 3001 ] --- 1 ---> [M A S T E R: 3000]
我在Worker.js
有N个工人,在master.js
有1个主人。 Master 和 worker 共享公共模块,而 master 有一个核心模块加载核心服务并在 PORT=3001
上暴露一个 api,一个 worker 在 PORT=3000
上加载其他 apis ,其中 Docker 容器已绑定。虽然 Worker 上的路由代理会将请求转发给 Master 以便为对核心模块的请求提供服务,但其他请求直接在 3000 上的服务器上。
启动脚本看起来像
'use strict';
(function() {
/// node clustering
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) { // master node
var masterConfig=require('./config/masterconfig.json');
// Fork workers.
var maxCPUs = process.env.WORKER_NUM || masterConfig.cluster.worker.num;
maxCPUs=(maxCPUs>numCPUs)?numCPUs:maxCPUs;
for (let i = 0; i < maxCPUs; i++) {
const worker=cluster.fork();
}
var MasterNode=require('./lib/master');
var master= new MasterNode(masterConfig);
master.start()
.then(done=> {
console.log(`Master ${process.pid} running on ${masterConfig.pubsub.node}`);
})
.catch(error=> { // cannot recover from master error
console.error(`Master ${process.pid} error`,error.stack);
process.exit(1);
});
}
else if (cluster.isWorker) { // worker node
var workerConfig=require('./config/workerconfig.json');
var WorkerNode=require('./lib/worker');
var worker= new WorkerNode(workerConfig);
worker.start()
.then(done=> {
console.log(`Worker ${process.pid} running on ${workerConfig.pubsub.node}`);
})
.catch(error=> { // worker error is recoverable
console.error(`Worker ${process.pid} error`,error.stack);
});
}
}).call(this);
我有以下问题。
1) 默认情况下,cluster
模块共享下划线 HTTP 连接使用 round-robin approach to serve requests - see here, where worker processes are spawned using the child_process.fork()。我不知道我是否可以自定义此方法来分发传入连接。
2) 到目前为止,我在 PORT=3000
上的每个 Worker 上的快速 Web 应用程序中提供静态文件、模板(如 pig/swig),这意味着我 运行 静态路由对于生成的每个工作实例上的 Web 应用程序。我不确定这是否是内存占用方面的最佳方法。
3) 其他聚类方法。我问过有关将此架构迁移到 PM2 的问题,尽管它看起来很有希望,但我不确定它是否是最佳选择 - 有关更多详细信息,请参阅 here。
master 应该只关心启动 worker 和关闭它们 properly/watching 以获取来自主机的信号并做出相应的响应。根据我的经验,我遇到了一些棘手的错误,因为我在 master 上暴露了一个本应在 worker 上的 API。
如果您打算切换到 PM2,PM2 将处理您的 master,您无论如何都需要将该代码移至 worker(或者至少过去是这样)
关于您的问题;
- 如果您需要覆盖 round-robin 或对其进行自定义,我认为您的目标是将相同的 client-traffic 路由到同一个工作人员,也就是 Sticky Sessions。 There are ways to do so 但有局限性;如果你在节点前使用像 nginx 或 haproxy 这样的反向代理(你应该这样做)并且还希望套接字按预期工作(并且在游戏中有 Docker),你不能真正分散工作人员因为您看到的 IP(您将在其上计算粘性 session id)将始终是您的代理或您的 docker 主机之一(即使使用 x-forwarded-for header) ,这首先违背了聚类的目的。
-> 我的解决方案是在新端口(例如 3001、3002 ... 300N)上启动每个工作人员并让 nginx 处理粘性 session handling
- 这不是问题,但并不理想 - 是的,内存会略有增加,因为每个工作人员都会加载路由和模块。但是 nginx 在处理静态文件(以及使用许多 http-headers 处理它的缓存)方面比 node 快得多。所以你应该依靠 nginx 服务静态并为动态请求保留节点(如 /api /login 等)
- PM2 是一个很好的解决方案,它具有许多高级功能,例如报告统计信息和处理 zero-downtime 部署,但也会根据您要使用的功能收取费用
我已经构建了一个 Node.js 内置 cluster 架构和 master/worker 配置。该应用程序使用 express
来提供 api 和静态文件,并使用 Docker:
[D O C K E R: 8080] --- N ---> [W O R K E R: 3001 ] --- 1 ---> [M A S T E R: 3000]
我在Worker.js
有N个工人,在master.js
有1个主人。 Master 和 worker 共享公共模块,而 master 有一个核心模块加载核心服务并在 PORT=3001
上暴露一个 api,一个 worker 在 PORT=3000
上加载其他 apis ,其中 Docker 容器已绑定。虽然 Worker 上的路由代理会将请求转发给 Master 以便为对核心模块的请求提供服务,但其他请求直接在 3000 上的服务器上。
启动脚本看起来像
'use strict';
(function() {
/// node clustering
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) { // master node
var masterConfig=require('./config/masterconfig.json');
// Fork workers.
var maxCPUs = process.env.WORKER_NUM || masterConfig.cluster.worker.num;
maxCPUs=(maxCPUs>numCPUs)?numCPUs:maxCPUs;
for (let i = 0; i < maxCPUs; i++) {
const worker=cluster.fork();
}
var MasterNode=require('./lib/master');
var master= new MasterNode(masterConfig);
master.start()
.then(done=> {
console.log(`Master ${process.pid} running on ${masterConfig.pubsub.node}`);
})
.catch(error=> { // cannot recover from master error
console.error(`Master ${process.pid} error`,error.stack);
process.exit(1);
});
}
else if (cluster.isWorker) { // worker node
var workerConfig=require('./config/workerconfig.json');
var WorkerNode=require('./lib/worker');
var worker= new WorkerNode(workerConfig);
worker.start()
.then(done=> {
console.log(`Worker ${process.pid} running on ${workerConfig.pubsub.node}`);
})
.catch(error=> { // worker error is recoverable
console.error(`Worker ${process.pid} error`,error.stack);
});
}
}).call(this);
我有以下问题。
1) 默认情况下,cluster
模块共享下划线 HTTP 连接使用 round-robin approach to serve requests - see here, where worker processes are spawned using the child_process.fork()。我不知道我是否可以自定义此方法来分发传入连接。
2) 到目前为止,我在 PORT=3000
上的每个 Worker 上的快速 Web 应用程序中提供静态文件、模板(如 pig/swig),这意味着我 运行 静态路由对于生成的每个工作实例上的 Web 应用程序。我不确定这是否是内存占用方面的最佳方法。
3) 其他聚类方法。我问过有关将此架构迁移到 PM2 的问题,尽管它看起来很有希望,但我不确定它是否是最佳选择 - 有关更多详细信息,请参阅 here。
master 应该只关心启动 worker 和关闭它们 properly/watching 以获取来自主机的信号并做出相应的响应。根据我的经验,我遇到了一些棘手的错误,因为我在 master 上暴露了一个本应在 worker 上的 API。
如果您打算切换到 PM2,PM2 将处理您的 master,您无论如何都需要将该代码移至 worker(或者至少过去是这样)
关于您的问题;
- 如果您需要覆盖 round-robin 或对其进行自定义,我认为您的目标是将相同的 client-traffic 路由到同一个工作人员,也就是 Sticky Sessions。 There are ways to do so 但有局限性;如果你在节点前使用像 nginx 或 haproxy 这样的反向代理(你应该这样做)并且还希望套接字按预期工作(并且在游戏中有 Docker),你不能真正分散工作人员因为您看到的 IP(您将在其上计算粘性 session id)将始终是您的代理或您的 docker 主机之一(即使使用 x-forwarded-for header) ,这首先违背了聚类的目的。 -> 我的解决方案是在新端口(例如 3001、3002 ... 300N)上启动每个工作人员并让 nginx 处理粘性 session handling
- 这不是问题,但并不理想 - 是的,内存会略有增加,因为每个工作人员都会加载路由和模块。但是 nginx 在处理静态文件(以及使用许多 http-headers 处理它的缓存)方面比 node 快得多。所以你应该依靠 nginx 服务静态并为动态请求保留节点(如 /api /login 等)
- PM2 是一个很好的解决方案,它具有许多高级功能,例如报告统计信息和处理 zero-downtime 部署,但也会根据您要使用的功能收取费用