Node.js集群最佳实践是什么?

5

在分叉工作进程之前还是之后编写服务器逻辑更好呢?

为了更好地说明问题,我会给出以下两个例子。

例子 #1:

const express = require("express");
const cluster = require('cluster');
const app = express();

app.get("/path", somehandler);

if (cluster.Master)
  // forking workers..
else
  app.listen(8000);

例如#2:
const cluster = require('cluster');

if (cluster.Master)
  // forking workers..
else {
  const express = require("express");
  const app = express();

  app.get("/path", somehandler);

  app.listen(8000);
}

什么是区别?
1个回答

2

没有区别。因为当您调用cluster.fork()时,它会在同一入口文件上调用child_process.fork并保留子进程处理程序以进行进程间通信。

请阅读以下方法,在集群主节点模块的下方行中定义:1671025152


让我们回到您的代码:

  1. 在示例#1中,它分配变量,为主进程和子进程创建应用程序实例,然后检查进程是否为主进程。

  2. 在示例#2中,它检查进程是否为主进程,如果不是,则为子工作进程分配变量,创建应用程序实例并绑定端口上的侦听器。


实际上,它会在子进程中执行相同的操作:

  1. 分配变量

  2. 创建应用程序实例

  3. 启动监听器


我使用集群的最佳实践有两个步骤:

第一步 - 在单独的模块中具有自定义集群包装器并将其包装在应用程序调用中:

拥有cluster.js文件:

'use strict';

module.exports = (callable) => {
  const
    cluster = require('cluster'),
    numCpu = require('os').cpus().length;

  const handleDeath = (deadWorker) {
    console.log('worker ' + deadWorker.process.pid + ' dead');

    const worker = cluster.fork();
    console.log('re-spawning worker ' + worker.process.pid);
  }
  
  process.on('uncaughtException',
    (err) => {
      console.error('uncaughtException:', err.message);
      console.error(err.stack);
    });

  cluster.on('exit', handleDeath);
  
  // no need for clustering if there is just 1 cpu
  if (numCpu === 1 || !cluster.isMaster) {
    return callable();
  }
  
  // saving 1 cpu for master process (1 M + N instances) 
  // or create 2 instances since 1 M + 1 Instance 
  // is ineffective when respawning instance
  // better to have 1 M + 2 instances if cpu count 2
  const instances = numCpu > 2 ? numCpu - 1 : numCpu; 

  console.log('Starting', instances, 'instances');
  for (let i = 0; i < instances; i++, cluster.fork());
};

保持像下面这样简单的app.js,以实现模块化和可测试性(阅读关于supertest的内容):
'use strict';

const express = require("express");
const app = express();

app.get("/path", somehandler);

module.exports = app;

将应用程序在某个端口上提供服务必须由不同的模块处理,因此需要让 server.js 看起来像这样:

'use strict';

const start = require('./cluster');

start(() => {
  
  const http = require('http');
  const app = require('./app');


  const listenHost = process.env.HOST || '127.0.0.1';
  const listenPort = process.env.PORT || 8080;
  const httpServer = http.createServer(app);

  httpServer.listen(listenPort, listenHost,
      () => console.log('App listening at http://'+listenHost+':'+listenPort));
});

scripts部分,您可以添加以下行到package.json文件中:

"scripts": {
  "start": "node server.js",
  "watch": "nodemon server.js",
  ...
}

使用以下命令运行应用程序:

node server.js, nodemon server.js

或者

npm start, npm run watch



步骤2 - 当需要容器化时:

保持代码结构与步骤1相同,并使用docker

集群模块将获得由容器或编排工具提供的CPU资源

并且作为额外的功能,您可以使用docker swarmkubernetesdc/os等按需扩展docker实例。

Dockerfile

FROM node:alpine

ENV PORT=8080
EXPOSE $PORT

ADD ./ /app
WORKDIR /app

RUN apk update && apk upgrade && \
    apk add --no-cache bash git openssh

RUN npm i
CMD ["npm", "start"]

如果有人给我点踩,请指出我在回答中做错了什么。 - num8er
谢谢您的快速回复,但是关于内存怎么办?我知道每个工作进程都有独立的内存,所以如果我在分叉之后再实例化app和其他变量,那么会消耗更多的内存吗?如果我没有这样做,这些变量会在工作进程之间共享吗? - Ammar Mourad
1
每个工作进程都拥有独立的内存 - 是的,因为使用了child_process.fork。如果我在fork之后实例化应用程序和其他变量,会消耗更多的内存吗?当您调用cluster.fork时,它会调用child_process.fork并再次运行同一文件。根据您的变量实例化方式,它可能被子进程或主进程消耗。因此,示例#2更值得推荐。 - num8er
1
第一个代码块中的“instances”变量似乎没有定义在任何地方,它是否应该改为“numCPUs”? - G07cha
1
@G07cha,好眼力,我已经更新了我的代码。 - num8er

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接