Koa开发:Node服务中非常重要的概念——进程管理
进程是非常重要的一个概念,尤其对于单线程的JavaScript来说,Node.js做好进程管理是在一些负载性能方面,能体现出巨大价值。尤其在支撑公司级别的一些大型项目,多进程管理是必不可少的。本文将结合最近的畅销书《Koa开发:入门、进阶与实战》中的内容,具体讲述Node.js进程相关的知识。
进程的概念
在一些面试中,面试官会经常问一个问题,进程和线程有什么区别。这是一个老生常谈的问题了,但是很多候选人的回答都比较书面化,比如这样的回答:进程是资源分配的最小单位,线程是CPU调度的最小单位。说实话,如果我是一个小白,听到这样的答案,我还是不能够理解进程和线程到底是什么。
其实我们每个人对于新概念的理解都是需要一些过程的,但是如果能够类比成比较熟悉的一些场景的话,理解会更快一点。比如,我们可以把一列火车理解为一个进程,一节车厢理解为一个线程。就是说,线程必须在进场上执行,就像单独的一节车厢无法运行,但是装在一列火车上就可以跑了。进程与进程间是互不干涉的,好比一列火车由于故障停运了,并不影响另一列火车正常运行,但是如果线程坏了,那进程也就崩掉了,可以理解,如果一节车厢坏掉了,那火车基本也就不能正常运行了。
创建多进程
在成熟的Node.js项目中,基本都会提供多进程功能,主要有两个好处:一是目前大多数的服务器都是多核的,为了更好利用服务器资源,提高性能优势;二是进程之间互不影响,万一其中一个进程崩掉,还有其他进程正常运行,不影响线上业务。
Node.js创建多进程共有三种方式:
● child_process.exec:使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。
● child_process.spawn:使用指定的命令行参数创建新进程。
● child_process.fork:是 spawn()的特殊形式,用于在子进程中运行的模块,如 fork('./son.js') 相当于spawn('node', ['./son.js']) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。
接下来,我们通过实例来对这三种创建方式进行理解。假设现在有一个主进程,要用子进程来执行一个命令,用child_process.exec这种方式的实现代码如下:
// exec.js
const child_process = require('child_process');
for(var i=0; i<3; i++) {
var workerProcess = child_process.exec('node command.js '+i, function (error, stdout, stderr) {
if (error) {
console.log(error.stack);
console.log('Error code: '+error.code);
console.log('Signal received: '+error.signal);
}
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
});
workerProcess.on('exit', function (code) {
console.log('子进程已退出,退出码 '+code);
});
}
子进程执行的文件只有标准日志输出,代码如下:
// command.js
console.log(`pid: ${process.pid}, 进程${process.argv[2]}的stdout`);
console.error(`pid: ${process.pid}, 进程${process.argv[2]}的stderr`);
执行exec.js,结果如图1所示。
图1 exec创建多进程执行结果
我们可以看到每个子进程pid是不一样的,说明exec方法确实创建出多个不同的进程来执行不同的任务。这种模式如果遇到子进程有大量数据输出的话,就不太合适了,这种情况用spawn来实现比较好,因为spawn的数据是通过流的方式返回的。代码如下:
// spawn.js
const child_process = require('child_process');
for(var i=0; i<3; i++) {
var workerProcess = child_process.spawn('node', ['command.js', i]);
workerProcess.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
workerProcess.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
workerProcess.on('close', function (code) {
console.log('子进程已退出,退出码 '+code);
});
}
执行结果如图2所示。
图2 spawn创建多进程执行结果
执行结果基本和exec方式执行结果类似。spawn尽管比exec的使用场景多了一些,但是对于主子进程频繁通信的场景支持得不好,这个时候就可以通过fork的方式创建子进程了。代码如下:
// fork.js
const child_process = require('child_process');
for(var i=0; i<3; i++) {
var worker_process = child_process.fork("command.js", [i]);
worker_process.on('close', function (code) {
console.log('子进程已退出,退出码 ' + code);
});
}
执行结果如图3所示。
图3 fork创建多进程执行结果
进程通信
在主从模式场景下,进程通信是避免不了的,那么在Node.js服务中,进程如何通信呢?这里介绍两种方式。
1)通过Node.js原生的IPC(Inter-Process Communication,进程间通信)来实现。这种方式比较普遍且通用,一般企业里的项目也是通过这种方式进行的进程间通信。下面通过一个实例来进行理解,主进程代码如下:
// master.js
const cp = require('child_process');
const n = cp.fork(`child.js`);
n.on('message', (msg) => {
console.log('主进程收到子进程的消息: ', msg);
});
// 主进程发送给子进程的消息
n.send('hello child process!');
子进程的实现代码如下:
process.on('message', (msg) => {
console.log('子进程收到主进程的消息:', msg);
});
// 给主进程发消息
process.send('hello master process!');
执行master.js,运行结果如图4所示。
图4 IPC主子进程通信
简单来说,IPC的进程通信方式一般是通过共享内存的方式实现的,使得多个进程可以访问同一块内存空间。
2)进程通信方式,可以通过Socket来进行通信。具体实例代码如下:
// master.js
const { spawn } = require('child_process');
const child = spawn('node', ['child'], {
// 子进程的标准输入输出配置
stdio: [null, null, null, 'pipe'],
});
child.stdio[1].on('data', data => {
console.log(`来自子进程消息 ${data.toString()}`);
});
子进程实现代码如下:
// child.js
const net = require('net');
const pipe = net.Socket({ fd: 1 });
pipe.write('hello master process!');
执行master.js,可以看到,子进程成功把消息发给了主进程。运行结果如图5所示。
图5 Socket主子进程通信
在Node知识体系中,进程管理是一个非常重要的概念,比如在实际业务场景中,在机器数量有限的情况下,如果想提升服务的QPS等性能指标,就可以通过创建多进程来提升请求的上限。除了进程管理,路由技巧、用户鉴权机制等也都是Koa在实际业务场景中涉及的高阶内容,如果您对此感兴趣,推荐您详细阅读刘江虹老师的新作《Koa开发:入门、进阶与实战》。
这是一本能指导你零基础掌握Koa完整知识体系并深入理解Node.js难点的著作,将带领你在成为全栈工程师的道路上迈出坚实的一步。以入门、进阶、实战为学习曲线,从基本用法、底层实现、常见场景解决方案、难点和重点等等角度对Koa和Node.js进行了详细的讲解。
作者介绍
刘江虹,字节跳动抖音电商前端架构师,目前主要负责业务架构中工程化等相关方向,拥有多年前端架构工作经验。独立开发过一款可对标Egg的BFF企业级框架,支撑公司线上服务超1000个,全栈前端技术专家,具有丰富的Node实战经验。著有畅销书《React.js实战》。
更多新书
书讯 | 4月书讯(下)| 上新了,华章
书讯 | 4月书讯(上)| 上新了,华章
Webpack实战:入门、进阶与调优(第2版)
工业物联网:平台架构、关键技术与应用实践
数据安全实践指南
Web渗透测试实战:基于Metasploit 5.0