nodejs多进程

《深入浅出NodeJS》—— 朴灵 玩转进程篇的部分笔记

多进程架构

使用nodejs自带进程相关的模块复制进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//master.js
const fork = require('child_process').fork;

let cpus = require('os').cpus();

for (let i = 0; i < cpus.length; i++) {
fork('./worker.js')
}

//worker.js
const http = require('http');

let port = Math.floor((Math.random() + 1 ) * 1000)

console.log(port);

http.createServer((req, res) => {
console.log(Math.floor(Math.random() * 100 + 10));
res.writeHead(200, {'Content-Type': 'text/plain'})
res.end('end');
}).listen(port);

node mastar.js 程序依据当前机器上的CPU数量,复制出数量相当的node进程数。每个进程监听1000-2000的随机四个端口。

  • 这种模式叫做主从模式
  • 这是典型的分布式架构中用于并行处理业务的模式,具有较好的可伸缩性和稳定性。
  • 主进程负责调度或管理工作进程,不负责具体的业务处理,进程趋于稳定。
  • 工作进程负责具体的业务处理,工作进程需要关注其稳定性。

创建子进程的方法

  1. spawn(): 启动一个子进程来执行命令。
  2. exec(): 启动一个子进程来执行命令,与spawn()不同的时其接口不同,他有一个回调函数获知子进程的状况。
  3. execFile(): 启动一个子进程来执行可执行文件。
  4. fork(): 与spawn()类似,不同在于它创建的node的子进程只需制定要执行的JS文件模块即可。
1
2
3
4
5
6
7
8
9
var cp = require('child_process');
cp.spawn('node', ['workder.js']);
cp.exec('node worker.js', (err, stdout, stderr) => {

});
cp.execFile('worker.js', (er, stdout, stderr) => {

});
cp.fork('./worker.js');

进程间通信

对于child_process模块,通过fork()或者其他API,创建子进程后,父进程与子进程之间建立IPC通道,通过IPC通道,父子进程之间通过message()和send()传递消息。

这是一种通过消息传递内容,而不是共享或直接操作相关资源的方法,比较轻量无依赖。
但是send()方法第二个参数已可以传递句柄(一种标示资源的引用),以实现更复杂的应用。

IPC

IPC(Inter-Process Communication)进程间通信。

实现进程间通信的方法有:命名管道,匿名管道,socket,信号量,共享内存,消息队列,Domain Socket等。

Nodejs中IPC由管道(PIPE)技术实现,具体细节由libuv提供,win下命名管道实现,* nix下Unix Domian Socket实现。

IPC创建步骤

  • 父进程创建子进程前,会先创建IPC并监听。
  • 创建子进程,并通过环境变量(NODE_CHANNEL_FD)告诉子进程这个IPC的文件描述符。
  • 子进程启动过程中,通过文件描述符去连接IPC通道。

只有子进程时node进程时,子进程才会连接IPC通道,其他类型的进程无法实现此种方法的进程通信,除非其他进程也按照上述约定去连接IPC通道。

句柄传递

问题:
每一个进程启动时监听不同的端口,主进程要接收所有的网络请求,再将请求代理到不同端口的子进程上,这种方法虽然可以避免端口重复的问题,甚至可以适当的均衡负载,但是由于进程每收到一个连接,将会用掉一个文件描述符,这种代理模式,则会浪费掉多出一倍的文件描述符。而操作系统的文件描述符是有限的,这种法师影响了系统的扩展能力。

解决方法:
通过send方法的第二个参数,可以向进程发送句柄(即标示某资源的引用),使主进程收到socket请求后,将这个请求直接发送给工作进程,而不是重新与工作进程建立新的socket连接来发送数据。

句柄传送
句柄传送
句柄传送

当主进程将服务器句柄发给子进程后,关闭服务器的监听,则会发现所有的子进程都能监听到同一个端口了。

句柄传送

相关解释

  • 发送的句柄不是真的对象,而是句柄文件描述符。
    由于底层细节不被应用层感知,所以在子进程中,开发者会有种服务器就是从父进程直接传递过来的感觉。Node进程之间只传递消息,不传递对象,这种错觉时抽象封装的结果。

  • Node目前只接受以下几种句柄类型:

    • net.Socket: TCP套接字
    • net.Server: TCP服务器
    • net.Native: C++层面的TCP套接字或IPC管道
    • dgram.Socket: UDP套接字
    • dgram.Native: C++层面的UDP套接字
  • 由于独立启动的进程直接并不知道文件描述符,所以监听相同端口会失败。但当使用send发送句柄从而还原出来的服务,它们的文件描述符时相同的,所以监听相同的端口不会引起异常。

  • 多个应用监听相同的端口时,文件描述符同一时间只能被一个进程使用,
    所以当有一个请求向服务端发送后,即使有多个应用监听同个端口,也只有一个进程为其服务,这些进程服务时 抢占式 的。

补补操作系统去→_→
…..