当前位置: 首页 > news >正文

Koa开发:Node服务中非常重要的概念——进程管理

872be6f03268b195d238f957f076149b.gif

进程是非常重要的一个概念,尤其对于单线程的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所示。

e402c19155e1a7e69b9ecbc46e27283e.png

图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所示。

ac2bf28484a3bd5e506800edca81458c.png

图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所示。 

3dd302d4a5ef941b26f9bbb5c7d22792.png

图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所示。

ebe8a1b5af2a7aeff734dc75a9668fbb.png

图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所示。

1c94c856b06c828d8588e4c266c4511e.png

图5 Socket主子进程通信

在Node知识体系中,进程管理是一个非常重要的概念,比如在实际业务场景中,在机器数量有限的情况下,如果想提升服务的QPS等性能指标,就可以通过创建多进程来提升请求的上限。除了进程管理,路由技巧、用户鉴权机制等也都是Koa在实际业务场景中涉及的高阶内容,如果您对此感兴趣,推荐您详细阅读刘江虹老师的新作《Koa开发:入门、进阶与实战》。

c6fa76e199281317bc7d4f328cfff739.png

这是一本能指导你零基础掌握Koa完整知识体系并深入理解Node.js难点的著作,将带领你在成为全栈工程师的道路上迈出坚实的一步。以入门、进阶、实战为学习曲线,从基本用法、底层实现、常见场景解决方案、难点和重点等等角度对Koa和Node.js进行了详细的讲解。

作者介绍

    刘江虹,字节跳动抖音电商前端架构师,目前主要负责业务架构中工程化等相关方向,拥有多年前端架构工作经验。独立开发过一款可对标Egg的BFF企业级框架,支撑公司线上服务超1000个,全栈前端技术专家,具有丰富的Node实战经验。著有畅销书《React.js实战》。

更多新书

书讯 | 4月书讯(下)| 上新了,华章

书讯 | 4月书讯(上)| 上新了,华章

Webpack实战:入门、进阶与调优(第2版)

工业物联网:平台架构、关键技术与应用实践

数据安全实践指南

Web渗透测试实战:基于Metasploit 5.0

155efaaeed3d3a7784b2cd2f7c8d4ba4.gif

相关文章:

  • 从loser到产品大牛,你经历的我都经历了
  • 读书会 | 为什么《数据中台:让数据用起来》,值得每个数据人读?
  • 重新思考企业架构
  • 概率为何反直觉?
  • 手把手教你用Pandas 合并两行为一行并调整行顺序
  • C++20 用微软的提案进入协程时代!
  • 构建系统软件三步走,这些书你不可错过!
  • 数字经济下半场中,金融企业应该如何进行数字化经营呢?
  • ClickHouse为啥在字节跳动能这么火?
  • 计算机如何大规模协作?
  • 终于有人把元数据讲明白了
  • Go语言精进之路:你知道什么是Go语言编程思维吗?
  • Java冷启动慢?不存在的!
  • 分布式系统一致性的本质,看这篇秒懂
  • 如何应对数字化转型这个大命题,这些书给你答案
  • 时间复杂度分析经典问题——最大子序列和
  • Centos6.8 使用rpm安装mysql5.7
  • ES6系列(二)变量的解构赋值
  • hadoop入门学习教程--DKHadoop完整安装步骤
  • IndexedDB
  • js作用域和this的理解
  • Node.js 新计划:使用 V8 snapshot 将启动速度提升 8 倍
  • npx命令介绍
  • Yeoman_Bower_Grunt
  • Zepto.js源码学习之二
  • 创建一种深思熟虑的文化
  • 检测对象或数组
  • 简单易用的leetcode开发测试工具(npm)
  • 区块链共识机制优缺点对比都是什么
  • 如何进阶一名有竞争力的程序员?
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 实战:基于Spring Boot快速开发RESTful风格API接口
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 用jquery写贪吃蛇
  • 运行时添加log4j2的appender
  • ​业务双活的数据切换思路设计(下)
  • #pragma预处理命令
  • #我与Java虚拟机的故事#连载10: 如何在阿里、腾讯、百度、及字节跳动等公司面试中脱颖而出...
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (PWM呼吸灯)合泰开发板HT66F2390-----点灯大师
  • (三)elasticsearch 源码之启动流程分析
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • .Net CoreRabbitMQ消息存储可靠机制
  • .NET LINQ 通常分 Syntax Query 和Syntax Method
  • .NET 将混合了多个不同平台(Windows Mac Linux)的文件 目录的路径格式化成同一个平台下的路径
  • .NET/ASP.NETMVC 大型站点架构设计—迁移Model元数据设置项(自定义元数据提供程序)...
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • .net利用SQLBulkCopy进行数据库之间的大批量数据传递
  • .NET面试题解析(11)-SQL语言基础及数据库基本原理
  • .NET中使用Protobuffer 实现序列化和反序列化
  • :not(:first-child)和:not(:last-child)的用法
  • @EnableConfigurationProperties注解使用
  • [16/N]论得趣
  • [ActionScript][AS3]小小笔记