深入剖析Node.js cluster模块


Posted in Javascript onMay 23, 2018

cluster模块概览

node实例是单线程作业的。在服务端编程中,通常会创建多个node实例来处理客户端的请求,以此提升系统的吞吐率。对这样多个node实例,我们称之为cluster(集群)。

借助node的cluster模块,开发者可以在几乎不修改原有项目代码的前提下,获得集群服务带来的好处。

集群有以下两种常见的实现方案,而node自带的cluster模块,采用了方案二。

方案一:多个node实例+多个端口

集群内的node实例,各自监听不同的端口,再由反向代理实现请求到多个端口的分发。

  1. 优点:实现简单,各实例相对独立,这对服务稳定性有好处。
  2. 缺点:增加端口占用,进程之间通信比较麻烦。

方案二:主进程向子进程转发请求

集群内,创建一个主进程(master),以及若干个子进程(worker)。由master监听客户端连接请求,并根据特定的策略,转发给worker。

  1. 优点:通常只占用一个端口,通信相对简单,转发策略更灵活。
  2. 缺点:实现相对复杂,对主进程的稳定性要求较高。

入门实例

在cluster模块中,主进程称为master,子进程称为worker。

例子如下,创建与CPU数目相同的服务端实例,来处理客户端请求。注意,它们监听的都是同样的端口。

// server.js
var cluster = require('cluster');
var cpuNums = require('os').cpus().length;
var http = require('http');

if(cluster.isMaster){
 for(var i = 0; i < cpuNums; i++){
  cluster.fork();
 }
}else{
 http.createServer(function(req, res){
  res.end(`response from worker ${process.pid}`);
 }).listen(3000);

 console.log(`Worker ${process.pid} started`);
}

创建批处理脚本:./req.sh。

#!/bin/bash

# req.sh
for((i=1;i<=4;i++)); do  
 curl http://127.0.0.1:3000
 echo ""
done

输出如下。可以看到,响应来自不同的进程。

response from worker 23735
response from worker 23731
response from worker 23729
response from worker 23730

cluster模块实现原理

了解cluster模块,主要搞清楚3个问题:

  1. master、worker如何通信?
  2. 多个server实例,如何实现端口共享?
  3. 多个server实例,来自客户端的请求如何分发到多个worker?

下面会结合示意图进行介绍,源码级别的介绍,可以参考 笔者的github。

问题1:master、worker如何通信

这个问题比较简单。master进程通过 cluster.fork() 来创建 worker进程。cluster.fork() 内部 是通过 child_process.fork() 来创建子进程。

也就是说:

  1. master进程、worker进程是父、子进程的关系。
  2. master进程、woker进程可以通过IPC通道进行通信。(重要)

问题2:如何实现端口共享

在前面的例子中,多个woker中创建的server监听了同个端口3000。通常来说,多个进程监听同个端口,系统会报错。

为什么我们的例子没问题呢?

秘密在于,net模块中,对 listen() 方法进行了特殊处理。根据当前进程是master进程,还是worker进程:

  1. master进程:在该端口上正常监听请求。(没做特殊处理)
  2. worker进程:创建server实例。然后通过IPC通道,向master进程发送消息,让master进程也创建 server 实例,并在该端口上监听请求。当请求进来时,master进程将请求转发给worker进程的server实例。

归纳起来,就是:master进程监听特定端口,并将客户请求转发给worker进程。

如下图所示:

深入剖析Node.js cluster模块

问题3:如何将请求分发到多个worker

每当worker进程创建server实例来监听请求,都会通过IPC通道,在master上进行注册。当客户端请求到达,master会负责将请求转发给对应的worker。

具体转发给哪个worker?这是由转发策略决定的。可以通过环境变量NODE_CLUSTER_SCHED_POLICY设置,也可以在cluster.setupMaster(options)时传入。

默认的转发策略是轮询(SCHED_RR)。

当有客户请求到达,master会轮询一遍worker列表,找到第一个空闲的worker,然后将该请求转发给该worker。

master、worker内部通信小技巧

在开发过程中,我们会通过 process.on('message', fn) 来实现进程间通信。

前面提到,master进程、worker进程在server实例的创建过程中,也是通过IPC通道进行通信的。那会不会对我们的开发造成干扰呢?比如,收到一堆其实并不需要关心的消息?

答案肯定是不会?那么是怎么做到的呢?

当发送的消息包含cmd字段,且改字段以NODE_作为前缀,则该消息会被视为内部保留的消息,不会通过message事件抛出,但可以通过监听'internalMessage'捕获。

以worker进程通知master进程创建server实例为例子。worker伪代码如下:

// woker进程
const message = {
 cmd: 'NODE_CLUSTER',
 act: 'queryServer'
};
process.send(message);

master伪代码如下:

worker.process.on('internalMessage', fn);

相关链接

官方文档:https://nodejs.org/api/cluster.html

Node学习笔记:https://github.com/chyingp/nodejs-learning-guide

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
关于文本限制字数的js代码
Apr 02 Javascript
javascript Firefox与IE 替换节点的方法
Feb 24 Javascript
jquery中dom操作和事件的实例学习-表单验证
Nov 30 Javascript
ajax页面无刷新 IE下遭遇Ajax缓存导致数据不更新的问题
Dec 11 Javascript
JS链式调用的实现方法
Mar 07 Javascript
JavaScript作用域链示例分享
May 27 Javascript
什么是 AngularJS?AngularJS简介
Dec 06 Javascript
JQuery的attr 与 val区别
Jun 12 Javascript
jQuery无刷新上传之uploadify3.1简单使用
Jun 18 Javascript
深入浅析JS是按值传递还是按引用传递(推荐)
Sep 18 Javascript
node+express+ejs使用模版引擎做的一个示例demo
Sep 18 Javascript
微信小程序选择图片和放大预览图片功能
Nov 02 Javascript
Node.js进阶之核心模块https入门
May 23 #Javascript
使用 vue-i18n 切换中英文效果
May 23 #Javascript
centos 上快速搭建ghost博客方法分享
May 23 #Javascript
20个最常见的jQuery面试问题及答案
May 23 #jQuery
AngularJS与BootStrap模仿百度分页的示例代码
May 23 #Javascript
jQuery发请求传输中文参数乱码问题的解决方案
May 22 #jQuery
vue的传参方式汇总和router使用技巧
May 22 #Javascript
You might like
php实现的发送带附件邮件类实例
2014/09/22 PHP
Yii框架关联查询with用法分析
2014/12/02 PHP
PHP foreach遍历多维数组实现方式
2016/11/16 PHP
用户注册常用javascript代码
2009/08/29 Javascript
js校验表单后提交表单的三种方法总结
2014/02/28 Javascript
原生javascript实现的分页插件pagenav
2014/08/28 Javascript
轻松学习jQuery插件EasyUI EasyUI创建树形网络(1)
2015/11/30 Javascript
基于JavaScript判断浏览器到底是关闭还是刷新(超准确)
2016/02/01 Javascript
AngularJS学习笔记之依赖注入详解
2016/05/16 Javascript
jQuery基础_入门必看知识点
2016/07/04 Javascript
原生JS+CSS实现炫酷重力模拟弹跳系统的登录页面
2017/11/01 Javascript
解决Vue.js 2.0 有时双向绑定img src属性失败的问题
2018/03/14 Javascript
使用vue2实现购物车和地址选配功能
2018/03/29 Javascript
Vue SSR 组件加载问题
2018/05/02 Javascript
不得不知的ES6小技巧
2018/07/28 Javascript
在vue里使用codemirror遇到的问题
2018/11/01 Javascript
ES6学习笔记之let与const用法实例分析
2020/01/22 Javascript
Vue使用自定义指令实现拖拽行为实例分析
2020/06/06 Javascript
vue组件中传值EventBus的使用及注意事项说明
2020/11/16 Javascript
Python中转换角度为弧度的radians()方法
2015/05/18 Python
Django跨域请求问题的解决方法示例
2018/06/16 Python
tensorflow 恢复指定层与不同层指定不同学习率的方法
2018/07/26 Python
python实现转盘效果 python实现轮盘抽奖游戏
2019/01/22 Python
python中的句柄操作的方法示例
2019/06/20 Python
Pandas之Fillna填充缺失数据的方法
2019/06/25 Python
python如何删除文件中重复的字段
2019/07/16 Python
如何将 awk 脚本移植到 Python
2019/12/09 Python
python使用HTMLTestRunner导出饼图分析报告的方法
2019/12/30 Python
美国牙科折扣计划:DentalPlans.com
2019/08/26 全球购物
意大利比基尼品牌:MISS BIKINI
2019/11/02 全球购物
Java面试中常遇到的问题,也是需要注意的几点
2013/08/30 面试题
中医专业应届生求职信
2013/11/17 职场文书
一年级学生评语
2014/04/23 职场文书
小学雷锋月活动总结
2014/07/03 职场文书
教导处教学工作总结
2015/08/12 职场文书
SpringBoot连接MySQL获取数据写后端接口的操作方法
2021/11/02 MySQL