node.js是单进程应用,要充分利用多核cpu的性能,就需要用到多进程架构。

作为web服务器,不能多个进程创建不同的socket文件描述符去accept网络请求, 有经验的同学知道,如果端口被占用了,再跑一个监听该端口的服务就会报EADDRINUSE异常。那么问题来了,多进程架构如何去解决这个问题?

我们把多进程架构设计成典型的master-workers架构, 一个master, 多个worker。

master-workers架构如下图所示:

我们可以在master进程代理accept请求然后分配给worker处理。但客户端进程连接到master进程,master进程连接到worker进程需要用掉两个文件描述符,会浪费掉一倍数量的文件描述符。

所以交由worker来accept请求会是更好的方案。

master先创建一个server监听端口,然后通过进程间通信,把socket文件描述符传递给所有的worker进程, worker进程用传递过来的socket文件描述符封装成server(感官上好像是把一个server对象发送给另一个进程,其实是把相应的句柄封装后,通过JSON.stringify()序列化再发送, 接收端进程还原成相应的句柄。)

然后,还有一个问题,假如其中一个worker进程异常退出了怎么办, 这个时候,worker进程应该要通知到master进程,然后master进程重新fork一个worker进程。

先上master的代码:

 "use strict"

 const fork = require('child_process').fork;
const cpus = require('os').cpus();
let server = require('net').createServer((socket)=>{
// ‘connection’ 监听器
socket.end('Handled by master \n');
console.error('Handled by master \n'); //不应该在master accept请求
}); server.listen(8001); let workers = {}; function createWorker(ser) {
let worker = fork('./worker.js'); worker.on('message', function(msg, handle) {
// 收到子进程通知需要创建新的worker(子进程退出前通知父进程)
if(msg ==='new_worker') {
let ser = handle;
createWorker(ser);
// 关掉
ser.close();
}
}) worker.on('exit', function(code, signal){
delete workers[worker.pid];
}); // 句柄转发
let result = worker.send('server', ser, (err)=> {err&&console.error(err)});
console.info('send server to child result:', result);
workers[worker.pid] = worker;
} for(let i=0; i<cpus.length; i++) {
createWorker(server);
} // 关掉,不再accept端口请求
server.close(); /*
code <number> The exit code if the child exited on its own.
signal <string> The signal by which the child process was terminated.
*/
process.on('exit', function(code, signal) {
console.log(`master exit, code:${code}, signal:${signal}`);
for(let pid in workers) {
workers[pid].kill();
}
}) process.on('uncaughtException', function(error) {
console.error('master | uncaughtException, error:', error);
process.exit(1);
}) //一些常用的退出信号的处理:
// kill pid 默认是SIGTERM信号
// 控制台 ctrl-c 是SIGINT信号
const killSignalList = ['SIGTERM', 'SIGINT'];
killSignalList.forEach((SIGNAL)=>{
process.on(SIGNAL, function(){
console.log(`${SIGNAL} signal`);
process.exit(1);
})
})

master进程根据cpu核数fork相应数量的worker进程, fork成功后马上把server句柄发送给worker进程, fork所有worker进程后, 就把server关掉,不再接收请求。 master进程退出前会调用worker的kill()方法杀掉所有worker进程。

worker代码如下:

 const http = require('http');

 const server = http.createServer(function(req, res) {
// ‘request’ 监听器
res.end('handled by worker \n');
// throw new Error('error');
}) let worker;
process.on('message', function(msg, handle){
if(msg === 'server') {
worker = handle;
worker.on('connection', function(socket){
server.emit('connection', socket);
})
} }) process.on('uncaughtException', function(err) {
console.error('uncaughtException err:', err.message, ', worker进程将重启');
// 通知master创建新的worker
process.send('new_worker', worker);
// 停止接收新的连接
worker.close(function() {
// 所有已有连接断开后,退出进程
process.exit(1);
});
});

worker进程有个细节处理的地方: 异常退出前,先通知master进程创建新的worker, 然后等待所有已有连接断开后再退出进程。

关于进程间的句柄发送功能, 有兴趣的同学可以再去了解一下, 子进程对象send(message,[sendHandle])方法可以发送的句柄类型有:

  • net.Socket,  TCP套接字。
  • net.Server,  TCP服务器,任意建立在TCP服务上的应用层服务都可以享受到它带来的好处。
  • net.Native, C++层面的TCP套接字或IPC通道。
  • dgram.Socket,  UDP套接字。
  • dgram.Native, C++层面的UDP套接字

多个worker进程监听同一个套接字,会导致惊群现象, 有请求过来时cpu会唤醒所有的worker进程, 最终只有一个进程accept到请求, 其它进程accept请求失败,这种情况会产生一些不必要的开销。 如何避免惊群现象,我另外写一篇文章具体说一下。

node.js多进程架构的更多相关文章

  1. 关于Node.js后端架构的一点后知后觉

    前言 上周有幸和淘宝前端团队的七念老师做了一些NodeJS方面上的交流(实际情况其实是他电话面试了我╮(╯-╰)╭),我们主要聊到了我参与维护的一个线上NodeJS服务,关于它的现状和当下的不足.他向 ...

  2. node.js express架构安装部署

    安装-g:表示全局安装(必须以安装node.js) npm install -g express-generator 创建一个express架构的项目文件夹express testWebApp 在pa ...

  3. 【nodejs原理&源码赏析(4)】深度剖析cluster模块源码与node.js多进程(上)

    [摘要] 集群管理模块cluster浅析 示例代码托管在:http://www.github.com/dashnowords/blogs 一. 概述 cluster模块是node.js中用于实现和管理 ...

  4. 【nodejs原理&源码赏析(4)】深度剖析cluster模块源码与node.js多进程(上)

    目录 一. 概述 二. 线程与进程 三. cluster模块源码解析 3.1 起步 3.2 入口 3.3 主进程模块master.js 3.4 子进程模块child.js 四. 小结 示例代码托管在: ...

  5. Node.js 多进程

    我们都知道 Node.js 是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能. 每个子进程总是带有三个流对象:child.st ...

  6. 【nodejs原理&源码赏析(6)】深度剖析cluster模块源码与node.js多进程(下)

    [摘要] cluster模块详解 示例代码托管在:http://www.github.com/dashnowords/blogs 阅读本章需要先阅读本系列前两章内容预热一下. 一. 引言 前两篇博文中 ...

  7. 【nodejs原理&源码赏析(6)】深度剖析cluster模块源码与node.js多进程(下)

    目录 一. 引言 二.server.listen方法 三.cluster._getServer( )方法 四.跨进程通讯工具方法Utils 五.act:queryServer消息 六.轮询调度Roun ...

  8. 深入理解 Node.js 进程与线程

    原文链接: https://mp.weixin.qq.com/s?__biz=MzAxODE2MjM1MA==&mid=2651557398&idx=1&sn=1fb991da ...

  9. Node.js:多进程

    ylbtech-Node.js:多进程 1.返回顶部 1. Node.js 多进程 我们都知道 Node.js 是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的 ...

随机推荐

  1. golang 包依赖管理 godep 使用

    介绍: godep是解决包依赖的管理工具,目前最主流的一种,原理是扫描记录版本控制的信息,并在go命令前加壳来做到依赖管理. 1.安装: go get github.com/tools/godep 2 ...

  2. 修改tomcat默认使用的jdk版本

    1.windows平台 在csetclasspath.bat文件开头加上如下两句(指定JDK): set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_79  se ...

  3. Charles抓取HTTPS数据包方法

    设置代理端口8888 ssl代理设置 允许所有地址连接 手机获取证书之前,先在电脑安装证书,需要信任.help-->ssl-proxying-->Install Charles Root ...

  4. Flex简记

    display的flex属性的使用对象分为1.容器即包含有元素的元素,2.项目,即某一个元素.就容器而言,flex可以实现换行(flex-wrap),改变容器内元素的排列方向(flex-directi ...

  5. Logback MDC

    Mapped Diagnostic Contexts (MDC)   (译:诊断上下文映射) Logback的设计目标之一是审计和调试复杂的分布式应用程序.大多数实际的分布式系统需要同时处理来自多个客 ...

  6. CSPS模拟 71

    全程傻眼 T1 毛衣衬 meet_in_middle.. 不再使用二分查找,而是直接枚举对面状态,虽然底数爆炸但是指数减半,复杂度是对的. T2 猫儿嗔 逆序关系有支配关系? $DAG$树.. 把逆序 ...

  7. 大数据之路day04_1--数组 and for循环进阶

    Java数组 在开始之前,提一个十分重要的一点:注意:在给数组分配内存空间时,必须指定数组能够存储的元素来确定数组大小.创建数组之后不能修改数组的大小,可以使用length属性获取数组的大小.在jav ...

  8. Python开发【第十三篇】高阶函数、递归函数、闭包

    函数式编程是指用一系列函数解决问题 好处:用每个函数完成每个细小的功能,一系列函数任意组合能够解决大问题 函数仅仅接收输入并产生输出,不包含任何能影响输出的内部状态 函数之间的可重入性 当一个函数的输 ...

  9. Scrapy进阶知识点总结(三)——Items与Item Loaders

    一.Items 抓取的主要目标是从非结构化源(通常是网页)中提取结构化数据.Scrapy蜘蛛可以像Python一样返回提取的数据.虽然方便和熟悉,但Python缺乏结构:很容易在字段名称中输入拼写错误 ...

  10. 802.11n速率集