深入浅出node.js第9章玩转进程摘录

9.1 服务模型的变迁

9.1.1 石器时代:同步

9.1.2 青铜时代:复制进程

9.1.3 白银时代:多线程

9.1.4 黄金时代:事件驱动

9.2 多进程架构

node提供了child_process模块,我们再将经典的示例代码存为worker.js文件,如下:

let http=require('http');
http.createServer(function (req,res) {
    res.writeHead(200,{'Content-Type':"text/plain"});
    res.end('hello world')
}).listen(Math.round((1+Math.random())*1000),'127.0.0.1');

将以下代码保存为master.js,并通过node master.js 启动它:

let fork=require('child_process').fork;
let cpus=require('os').cpus();
for(let i=0;i<cpus.length;i++){
    fork('./work.js');
}

在*nix的系统下通过ps aux|grep worker.js查看进程数量。
这就是master-worker模式,又称主从模式。
这里写图片描述
fork复制的进程都是独立的,这个进程有着独立而全新的V8实例。fork进程是昂贵的。

9.2.1 创建子进程

新版的node里面子进程也有同步子进程和异步子进程了,这里指的是异步子进程
- spawn():启动一个子进程
- exec():启动一个子进程,与spawn的不同是他有一个回调函数
- execFile():启动一个子进程来执行可执行文件
- fork():与spawn类似,不同点在于他创建node子进程只需要指定js文件即可

spawn与exec、execFile的不同是,后两者创建时可以指定timeout属性设置超时时间,时间到之后可以杀死子进程。
exec与execFile不同的是,exec适合执行已有的命令,execFile适合执行文件。

let cp=require('child_process');
cp.spawn('node',['worker.js']);//没有回调函数
cp.exec('node worker.js',function (err,stdout,stderr) {

});
cp.execFile('worker.js',function (err,stdout,stderr) {

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

这里写图片描述

9.2.2 进程间通信

parent.js:

let cp=require('child_process');
let n=cp.fork(__dirname+ './sub.js');
n.on('message',function (m) {

});
n.send({hello:'world'});

sub.js:

process.on('message', function (m) {
    console.log('child got message', m);
});
process.send({foo: 'bar'});
进程间通信原理

IPC,Inter-Process Communication,即进程间通信。Node实现IPC通道的是管道(pipe)技术。表现在应用层的进程间通信只有简单的message事件和send方法,十分简洁和消息化。
这里写图片描述
IPC 通道是用命名管道或Domain Socket创建的,与网络socket的行为类似,属于双向通信。在node中,IPC通道被抽象为stream对象,在调用send时发送消息(类似与write()),接收到的消息会通过message事件(类似与data)触发给应用层。

只有启动的子进程是node进程时,子进程才会根据环境变量去连接IPC通道,对于其他类型的子进程则无法实现进程间通信,除非其他进程也按照约定去连接这个已经创建好的IPC通道。

9.2.3 句柄传递

2.端口共同监听
node底层对每个端口监听都设置了SO_REUSEADDR选项,含义是不同的进程可以就相同的网卡和端口进行监听,这个服务器端套接字可以被不同的进程服用。
由于独立启动的进程相互之间不知道文件描述符,所以监听相同端口时会失败。但对于发送的句柄还原出来的服务而言,他们的文件描述符是相同的,所以不会引起异常。
多个原因监听相同的端口,文件描述符同一时间只能被某个进程所有。换言之就是网络请求向服务器发送时,只有一个幸运的进程能够抢到连接,也就是说只有他能为这个请求进行服务。这些进程服务是抢占式的。

9.3 集群稳定之路

9.4 cluster模块

const cluster = require('cluster');
cluster.setupMaster({
  exec: 'worker.js',
  args: ['--use', 'https'],
  silent: true
});
cluster.fork(); // https worker
cluster.setupMaster({
  exec: 'worker.js',
  args: ['--use', 'http']
});
cluster.fork(); // http worker

就官方文档而言,他更喜欢如下形式:

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`主进程 ${process.pid} 正在运行`);

  // 衍生工作进程。
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`工作进程 ${worker.process.pid} 已退出`);
  });
} else {
  // 工作进程可以共享任何 TCP 连接。
  // 在本例子中,共享的是一个 HTTP 服务器。
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('你好世界\n');
  }).listen(8000);

  console.log(`工作进程 ${process.pid} 已启动`);
}

我建议用cluster.setupmaster()这个API,将主进程和工作进程从代码完全剥离。

9.4.1 cluster工作原理

cluster模块就是child_process和net模块的组合应用。cluster启动时,他会在内部启动tcp服务器,在cluster.fork()子进程时,将这个tcp服务器端socket的文件描述符发送给工作进程。如果进程是通过cluster.fork()复制出来的,那么他的环境变量里面就存在NODE_UNIQUE_ID,如果工作进程中存在listen()侦听网络端口的调用,他将拿到该文件描述符,通过SO_REUSEADDR进行端口重用,从而实现多个子进程共享端口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值