# NodeJS中的多进程、集群

进程是 资源分配 的最小单位,线程是 CPU调度 的最小单位

Node下的js执行环境是单进程、单线程运行(这里的单线程指的是它的JS引擎是单线程),但可以通过clusterchild_process开启 多进程 来充分利用多核CPU。

# child_process

Node多进程模式 是通过child_process模块可以创建子进程来实现的。

模块里有4种方法(其中后面3个方法基于spawn进行封装):

  • spawn

  • fork

  • execFile

  • exec

以上方法都会返回一个childProcess对象。

该对象实现了EventEmitter接口,带有stdout, stdin, stderr对象。

# child_process.spawn

因为forkexecexecFile都是基于spawn进行的封装,所以spawn是基础。

# 标准进程通信

// process.js
const spawn = require('child_process').spawn;
const path = require("path");

// 创建子进程
let child = spawn('node', ['sub-process.js'], {
    cwd: path.join(__dirname),
    stdio: [0, "pipe", "ignore", "ipc"]
});

child.on('message', data => {
    console.log(data);

    // 回复消息给子进程
    child.send('world');

    // 杀死子进程
    // process.kill(child.pid);
});
// hello
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// sub-process.js
// 给主进程发送消息
process.send('hello');

// 接收主进程回复的消息
process.on('message', data => {
    console.log(data);

    // 退出子进程
    process.exit();
});
1
2
3
4
5
6
7
8
9
10
11

这种方式被称为 标准进程通信。通过给optionsstdio数组,配置ipc

  • 主进程监听子进程的message事件进行接收;在回调中利用子进程的send方法发送消息
  • 子进程监听自身的message事件进行接收;调用自身的send方法发送消息

指定要用message事件

# 退出、杀死子进程

因为子进程在监听message事件时,主进程不知道其什么时候完成,所以主进程会卡住。

此时需要 子进程内退出process.exit()主进程杀死process.kill(child.pid)

# 独立子进程

因为child_process模块创建的子进程是被主进程统一管理的(主进程死,子进程也得死)。

但可以实现子进程的独立:

  • 创建时,在options声明detached: truestdio: 'ignore'
  • 创建后,执行child.unref()

缺点:独立的子进程不能和主进程进行标准进程通信,即不能设置 ipc。(不能和主进程共用标准输入、标准输出、错误输出)

# child_process.fork

forkspawn进行了一层简易的封装:少传了一些默认参数、多了个silent

实际上返回子进程也是在内部执行spawn后返回。

fork 创建的子进程可以直接通过 send 方法和监听 message 事件与主进程进行通信。

# silent

silent实际上是:默认使用ipc通信,以及stdio的两种极端情况:

  • silent = true:将“标准输入、标准输出、错误输出”置为ignore
  • silent = false:将“标准输入、标准输出、错误输出”置为0, 1, 2

# child_process.execFile、child_process.exec

execFile基于spawn封装,可以 创建子进程后,直接进行文件操作

exec基于execFile封装,可以 创建子进程后,直接执行命令

exec常见场景:webpack-dev-server等命令行工具在启动本地服务器时,自己打开浏览器

const { execFile, exec } = require("child_process");

let execFileChild = execFile("node", ["--version"], (err, stdout, stderr) => {
    if (error) throw error;
    console.log(stdout);
    console.log(stderr);
});

let execChild = exec("node --version", (err, stdout, stderr) => {
    if (err) throw err;
    console.log(stdout);
    console.log(stderr);
});
1
2
3
4
5
6
7
8
9
10
11
12
13

区别:传参。

# cluster

cluster是NodeJS提供用来 实现多进程 的模块,它集成了 child_process 创建子进程的方法(比 ipc 更加简洁)。

# 通过cluster实现集群

集群指的是,负责接收并响应请求的多台服务器的集合。

NodeJS是 通过创建多进程 来实现集群的。

const cluster = require("cluster");
const http = require("http");
const os = require("os");

// 判断当前执行的进程是否为主进程:
// 主进程:创建子进程
// 子进程:监听服务
if (cluster.isMaster) {
    // 创建子进程
    os.cpus().forEach(() => cluster.fork());
} else {
    // 创建并监听服务
    http.createServer((req, res) => {
        res.end(`child${process.pid}`);
    }).listen(3000);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 主进程执行时,会通过cluster.fork()创建子进程
  • 子进程被创建后,该文件会再次执行(但此时cluster.isMaster为false),开始创建并监听服务。

# 多个进程为什么可以监听同一个端口?

理论上不允许,会出现端口冲突。

实际上,端口只会被 主进程 绑定监听一次。 然后在和子进程建立IPC通道后,会通过子进程的send方法Socket(也就是server)传给子进程实现 端口共享

当接收到请求后,主进程 会通过 负载均衡 来分配请求到各个子进程中。

# 多个进程间的通信?

进程间通信有几种:pipe(管道)、消息队列、信号量、Domain Socket。

在NodeJS中,是通过pipe实现进程间的通信的。(将一个进程的输出做为另外一个进程的输入)

好处:

  • 进程通信比较简单
  • 减少端口的资源浪费
  • 对 主进程 稳定性要求高

# 常用场景

pm2

# NodeJS的单线程

NodeJS下的JS执行环境是单线程的,只是通过cluster/child_process提供了一个通过创建多进程来模拟多线程的方式。

Node v10.5.0提出了一个概念 worker_threads,可以让NodeJS具有多线程的能力。

# 参考链接

NodeJS中的多进程、集群 (opens new window)

Nodejs 进阶:解答 Cluster 模块的几个疑问 (opens new window)

10道Nodejs进程相关 (opens new window)

更新时间: 11/21/2021, 2:45:24 AM