nodejs cluster模块初探
大家都知道nodejs是一个单进程单线程的服务器引擎,不管有多么的强大硬件,只能利用到单个CPU进行计算。所以,为了使用多核cpu来提高性能 就有了cluster,让node可以利用多核CPU实现并行。
随着nodejs的发展,让nodejs上生产环境,就必须是支持多进程多核处理!在V0.6.0版本,Nodejs内置了cluster的特性。自此,Nodejs终于可以作为一个独立的应用开发解决方案,映入大家眼帘了。
这个包不用安装,node默认自带。
引入cluster
const cluster = require('cluster');
如果你有多进程使用的经验,就一定知道一般来说很多多进程的模型都是 一个主进程,然后fork一堆工作进程。我们这里也不例外。 下面看一个简单的例子。
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`);
});
} else {
http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
}
这个demo首先引入了 cluster 然后判断是否是主进程,如果是的话 进入主进程 创建 cpu个数 个子进程。然后监听子进程exit
子进程if判断未通过,所以进入else 执行创建http代码
这样就可以简单的实现了一个单机多进程的httpd服务器。
#### 初窥
nodejs把进程封装的很全面,所以以前有c基础的同学再看node就会感觉非常简单。大家都知道在c里面 我们是通过fork的返回值判断到底是主进程还是子进程 那么在node里面 isMaster 到底是何方神圣呢。
其实node会在子进程里面添加一个环境变量
module.exports = ('NODE_UNIQUE_ID' in process.env) ?
require('internal/cluster/child') :
require('internal/cluster/master');
只需要判断当前进程有没有环境变量“NODE_UNIQUE_ID”就可知道当前进程是否是主进程;而变量“NODE_UNIQUE_ID”则是在主进程fork子进程时传递进去的参数,因此采用cluster.fork创建的子进程是一定包含“NODE_UNIQUE_ID”的。
消息通信
nodejs为我们屏蔽了底层的多进程信息通信,提供了一个 `send` 和 `on` 事件来传递消息,不过只能子进程和父进程传递,如果我们要2个子进程传递的话必须通过主进程做中转。
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) {
var worker = cluster.fork();
(function (i) {
workers[i].on('message', function(message) {
if (message.cmd == 'start') {
console.log(JSON.stringify(message));
}
});
workers[i].send({cmd:'callback',msg:"收到收到"});
})(i);
}
cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`);
});
} else {
http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
process.send({cmd: 'start',msg:"我已准备好!"});
}).listen(8000);
}
上面代码我们通过 子进程 `process.send` 发送给父进程,然后父进程接收到消息后 通过`send`回传的一个简单demo。
疑惑,多个子进程可以监听一个端口吗?
大家看到上面的几个例子,都是多个子进程监听同一个8000端口,那么有同学会有疑问,多个socket是否可以同时监听一个端口呢.
其实 cluster 实现同时多个进程监听一个端口,有两种方法。
1、 RoundRobin 默认*
主进程每fork一个子进程,都会调用handoff函数,进入该子进程的处理循环中。一旦主进程没有缓存的客户端请求时(this.handles为空),便会将当前子进程加入free空闲队列,等待主进程的下一步调度。这就是cluster模式的RoundRobin调度策略,每个子进程的处理逻辑都是一个闭环,直到主进程缓存的客户端请求处理完毕时,该子进程的处理闭环才被打开。在这个模式中,其实子进程 listen是个假的,并不会真正的监听端口,而是主进程来监听端口,如果有请求过来,就会调用其中的一个子进程来处理。

2、shared socket (windows默认)
shared socket策略(后文简称SS策略)采用SS策略调度算法,子进程的服务器工作逻辑完全不同于上文中所讲的那样,子进程创建的TCP服务器会在底层侦听端口并处理响应,这是如何实现的呢?SS策略的核心在于IPC传输句柄的文件描述符,并且在C++层设置端口的**SO_REUSEADDR(端口复用)** 选项,最后根据传输的文件描述符还原出handle(net.TCP),处理请求。这正是shared socket名称由来,共享文件描述符。
既然SS策略传递的是master进程的服务端socket的文件描述符,子进程侦听该描述符,那么由谁来调度哪个子进程处理请求呢?这就是由操作系统内核来进行调度。可是内核调度往往出现意想不到的效果,在linux下导致请求往往集中在某几个子进程中处理。这从内核的调度策略也可以推算一二,内核的进程调度离不开上下文切换,上下文切换的代价很高,不仅需要保存当前进程的代码、数据和堆栈等用户空间数据,还需要保存各种寄存器,如PC,ESP,最后还需要恢复被调度进程的上下文状态,仍然包括代码、数据和各种寄存器,因此代价非常大。而linux内核在调度这些子进程时往往倾向于唤醒最近被阻塞的子进程,上下文切换的代价相对较小。而且内核的调度策略往往受到当前系统的运行任务数量和资源使用情况,对专注于业务开发的http服务器影响较大,因此会造成某些子进程的负载严重不均衡的状况。那么为什么cluster模块默认会在windows机器中采用SS策略调度子进程呢?原因是node在windows平台采用的IOCP来最大化性能,它使得传递连接的句柄到其他进程的成本很高,因此采用默认的依靠操作系统调度的SS策略。
今天就大概写到这里,其实我对cluster了解的也不算很多,这篇文章就当是个抛砖引玉吧。欢迎大家一起来共同学习。
cluster 常用对象
cluster的各种属性和函数
cluster.setttings:配置集群参数对象
cluster.isMaster:判断是不是master节点
cluster.isWorker:判断是不是worker节点
Event: 'fork': 监听创建worker进程事件
Event: 'online': 监听worker创建成功事件
Event: 'listening': 监听worker向master状态事件
Event: 'disconnect': 监听worker断线事件
Event: 'exit': 监听worker退出事件
Event: 'setup': 监听setupMaster事件
cluster.setupMaster([settings]) : 设置集群参数
cluster.fork([env]): 创建worker进程
cluster.disconnect([callback]): 关闭worket进程
cluster.worker: 获得当前的worker对象
cluster.workers: 获得集群中所有存活的worker对象 worker对象
worker的各种属性和函数:可以通过cluster.workers, cluster.worket获得。
worker.id: 进程ID号
worker.process: ChildProcess对象
worker.suicide: 在disconnect()后,判断worker是否自杀
worker.send(message, [sendHandle]): master给worker发送消息。注:worker给发master发送消息要用process.send(message)
worker.kill([signal='SIGTERM']): 杀死指定的worker,别名destory()
worker.disconnect(): 断开worker连接,让worker自杀
Event: 'message': 监听master和worker的message事件
Event: 'online': 监听指定的worker创建成功事件
Event: 'listening': 监听master向worker状态事件
Event: 'disconnect': 监听worker断线事件
Event: 'exit': 监听worker退出事件
参考文档
Nodejs cluster模块深入探究
https://cnodejs.org/topic/596ffb9b3f0ab31540ed4b91
官方文档
https://nodejs.org/api/cluster.html
nodejs cluster模块初探的更多相关文章
- Nodejs cluster模块深入探究
由表及里 HTTP服务器用于响应来自客户端的请求,当客户端请求数逐渐增大时服务端的处理机制有多种,如tomcat的多线程.nginx的事件循环等.而对于node而言,由于其也采用事件循环和异步I/O机 ...
- Nodejs中cluster模块的多进程共享数据问题
Nodejs中cluster模块的多进程共享数据问题 前述 nodejs在v0.6.x之后增加了一个模块cluster用于实现多进程,利用child_process模块来创建和管理进程,增加程序在多核 ...
- nodejs(四) --- cluster模块详解
什么是cluster模块,为什么需要cluster模块? cluster在英文中有集.群的意思. nodejs默认是单进程的,但是对于多核的cpu来说, 单进程显然没有充分利用cpu,所以,node ...
- nodejs中的子进程,深入解析child_process模块和cluster模块
Node.js的进程管理 node遵循的是单线程单进程的模式,node的单线程是指js的引擎只有一个实例,且在nodejs的主线程中执行,同时node以事件驱动的方式处理IO等异步操作.node的 ...
- 【nodejs原理&源码赏析(4)】深度剖析cluster模块源码与node.js多进程(上)
[摘要] 集群管理模块cluster浅析 示例代码托管在:http://www.github.com/dashnowords/blogs 一. 概述 cluster模块是node.js中用于实现和管理 ...
- 【nodejs原理&源码赏析(4)】深度剖析cluster模块源码与node.js多进程(上)
目录 一. 概述 二. 线程与进程 三. cluster模块源码解析 3.1 起步 3.2 入口 3.3 主进程模块master.js 3.4 子进程模块child.js 四. 小结 示例代码托管在: ...
- 【nodejs原理&源码赏析(6)】深度剖析cluster模块源码与node.js多进程(下)
[摘要] cluster模块详解 示例代码托管在:http://www.github.com/dashnowords/blogs 阅读本章需要先阅读本系列前两章内容预热一下. 一. 引言 前两篇博文中 ...
- 【nodejs原理&源码赏析(6)】深度剖析cluster模块源码与node.js多进程(下)
目录 一. 引言 二.server.listen方法 三.cluster._getServer( )方法 四.跨进程通讯工具方法Utils 五.act:queryServer消息 六.轮询调度Roun ...
- Node.js的cluster模块——Web后端多进程服务
众所周知,Node.js是单线程的,一个单独的Node.js进程无法充分利用多核.Node.js从v0.6.0开始,新增cluster模块,让Node.js开发Web服务时,很方便的做到充分利用多核机 ...
随机推荐
- PostgreSQL=>递归查询
PostgreSQL=>递归查询 转载请注明源地址:http://www.cnblogs.com/funnyzpc/p/8232073.html 距上次博客更新刚好两周,这两周发生了很多,比如: ...
- 类A是公共的,应在名为A.java的文件中声明错误
第一种!!! “类A是公共的,应在名为A.java的文件中声明”这句话需要分两步来理解: 1.如果类A被声明为公共的(public),那么必须将类A保存在名为A.java的文件中: 2.反之,在一个文 ...
- HDU 1541 树状数组
树状数组入门博客推荐 http://blog.csdn.net/qq_34374664/article/details/52787481 Stars Time Limit: 2000/1000 MS ...
- 【自制工具类】Java删除字符串中的元素
这几天做项目需要把多个item的id存储到一个字符串中,保存进数据库.保存倒是简单,只需要判断之前是否为空,如果空就直接添加,非空则拼接个"," 所以这个字符串的数据结构是这样的 ...
- PHP中file_exists()函数不能检测包含中文的文件名的解决办法
版权声明:本文为博主原创文章,未经博主允许不得转载. PHP中一般使用file_exists()判断某个文件或者文件夹是否存在,如果文件或文件夹存在则返回true,不存在则返回fal ...
- PHP批量去除bom头代码的小工具
在 aitecms 群里有网友抱怨了好几天说本地的验证码一直无法显示,后来听说解决了,问其如何解决的,说是去除了文件 bom 就好了.后来百度到一篇文章也说 dedecms 的验证码不能显示,某次解决 ...
- DT_修改注册项
所用语法: /* */用于注释掉某些非必要的判断语句 修改的文件及路径如下: 前台: 文件路径:/根目录/lang/zh-cn/ 编辑文件:member.inc.php 修改其中一些提示信息(如,将[ ...
- IOS学习:隐藏键盘方法
1.点击界面的其它空白地方隐藏 由于UIViewController是继承自UIResponder的,所以可以覆写- (void)touchesBegan:(NSSet *)touches ...
- 批处理注册dll时候 遇到错误:模块已加载,但对***dll的调用失败
解决方法 在批处理的第一行加入:cd /d %~dp0 然后在批处理上右键选择使用管理员权限运行
- git 签出(恢复)指定文件
在项目开发中,偶尔会因为误删文件或其他原因需要从git仓库中恢复某些文件.此篇文章将介绍如何通过git从历史提交记录.分支记录恢复指定文件. 1. git checkout 说明:使用git chec ...