认识node核心模块--全局对象及Cluster

原文地址在我的博客,转载请注明出处,谢谢!

node 模块是node 完成强大功能的实现者。node 的核心模块包括events、fs、buffer、stream、cluster、http、net、一些操作OS和工具模块、全局对象等。本文将在node核心特性理解的基础上进一步深入探讨node核心模块的具体细节。本文主要探讨的模块有:全局对象global及其重要属性、多进程cluster、events重要类EventEmitter、流Stream、文件系统fs、网络http,还会介绍node框架express相关。

概述

本文先来介绍全局对象global及其重要属性、多进程cluster模块。

正文

node全局对象global

与浏览器对应的window一样,在node中global是全局对象,在全局作用域定义的任何变量都会保存为global的属性,称为全局变量。下面是global一些重要的属性:

  • 模块:modulerequireexports

    这三个全局变量组成了node 的模块定义和引入,是 commonJS 的实现。node将每个文件视为一个模块,在执行到每个模块之前都会定义好上述三个变量,因此可以直接使用。来看它们之间的协作:

    // module1.js
    exports.fun = function(a,b) {
        return a + b;
    } //绑定在exports的属性可以被其他模块引入使用
    
    //module2.js
    var module1 = require('./module1')
    module1.fun(3,2)  //5
    

    相关机制:

    • module 表示对这个模块的引用,因此module 实际上不是全局的,而是每个模块本地的。module除了exports 还有其他关于模块的属性,例如module.children
    • exports 其实是module.exports的简写,表示这个模块的输出。有一点需要注意,对exports直接赋值exports = {...}并不会被输出,因为exports事先已经被定义了,再次这样赋值会被覆盖,需要带上module:module.exports = {...}
    • require 表示引入某个模块,填写模块路径即可,在node_modules里面的模块填写名字即可,js文件可以不用写扩展名。

    另外,由于V8引擎对ES6的不断支持,node 中也可以直接使用ES6的一些特性、例如promise、class等,ES6的模块也被node 实验性的引入并且是稳定的。详情见nodejs中文网。可以在node.green查看支持的特性

  • 异步操作 setTimeout、setInterval、setImmediate、process.nextTick

    • setTimeout和setInterval跟浏览器端一样,不同的是,node实现了setImmediate(目前浏览器端只有 IE实现了该方法)。它表示在 Node.js 事件循环的当前回合结束时要调用的函数,用来把一些需要长时间运行的操作放在一个回调函数里,在node主线程完成后面的其他语句后,就立刻执行这个回调函数,参数是一个函数和用作这个函数的参数,作用跟setTimeout(fn,0)差不多,都是相当于立即在事件队列末尾插入一个事件,但也有差别。

    • process.nextTick(fn,...args)表示在当前调用栈结束后,在下一个事件执行前调用回调函数。node 提供这个API是为了把复杂耗时的任务放到最后去处理,以便优先执行简单的任务。来看它们之间的比较:

      setTimeout(() => console.log("setTimeout0"),0)
      setImmediate(() => console.log("setImmediate"))
      process.nextTick(() => console.log("nextTick"))
      //输出nextTick setTimeout0 setImmediate 或者 nextTick setImmediate setTimeout0 
      

      无论process.nextTick写在什么地方,它总是第一个输出。无论setTimeout和setImmediate谁先谁后,都可能出现两种结果,其中setTimeout(fn,0)先于setImmediate多一点。这是因为它们三个产生的事件推入到了不同的watcher(观察者)中—— setTimeout推入到了定时器观察者,setImmediate是check观察者,而process.nextTick()是idle观察者 ,而node主线程在事件循环时调用watcher 的顺序一般是 idle观察者 > check观察者,idle观察者 > 定时器观察者,check 和定时器不分先后,但定时器先于check的概率大一点。

  • node进程: process

    process 是node对进程的表示,提供了操作进程的接口,可以用process来提供进程有关信息,控制进程 。

    process提供的接口包括

    描述进程的一些状态(事件):exit、beforeExit、uncaughtException、Signal

    进程退出返回的状态码:Uncaught Fatal Exception、Signal Exits、Unused等

    进程的相关信息:stdout、stderr、config、stdin、exitCode、pid(进程编号)等

    操作进程的方法:abort、chdir、cwd、kill(发送信号给进程)、exit、nextTick、getgid、setgid、uptime等

  • 其他: console、__filename__dirname

    这些都是全局变量,可以在任何地方引用

node 子进程:child_process模块

一个进程只能利用一个CPU时间分片,为了高效利用多核CPU,node 提供了可以创建子进程(注意不是子线程)child_process模块,来帮助主进程高效利用多核CPU完成其他复杂的任务。之所以提供创建子进程而不是子线程的接口,是因为这让我们的程序状态单一,不用在意状态同步、死锁、上下文切换开销等等多线程编程中的头疼问题。这样以来一个进程只有一个线程。虽然单线程也会带来一些问题,如错误会引起整个应用退出等,但这都有了很好的解决方案。

创建子进程

node有三种创建子进程的接口:

  • exec / execFile: 这两者都会创建子进程来执行,执行的结果会存储在Buffer中。不同的是前者创建一个shell进程来执行命令,后者直接创建一个进程用来执行可执行文件,因此前者适合用来执行shell命令然后获取输出,后者效率较高。这两者因为输出的结果存储在Buffer中,因此只适合输出轻量的数据。
  • spawn:适合用于进程输入、输出数据量比较大的情况,因为它支持以 stream 的形式输入输出,可以用于任何命令。spawn的参数option有一个stdio配置项,用来配置子进程与父进程之间的IO通道,还有一个detached来配置子进程是否独立运行,可以创建常驻后台进程。总之,给予了子进程更多的灵活性。
  • fork:fork 其实是spawn的特例,它会创建一个V8实例,只能用来运行node.js程序,并且与上面两种方法不同的是,fork会在父子进程间建立 IPC 通道,父子进程之间可以互相收发消息——通过监听 message 事件和调用 send 方法,就可以在父子进程间进行通信了。

父子进程通信

首先,这三种API都返回ChildProcess实例,因此都可以通过访问stdout属性来得到输出,exec/execFile 接口还可以在参数里绑定回调函数拿到子进程的stdout 。

const { exec, execFile, spawn, fork } = require("child_process")

// exec/execFile 接口既可以在参数里绑定回调函数拿到输出流,也可以利用返回的ChildProcess实例
const exec_process = exec("node child_process.js", {}, (err, stdout, stderr) => {
    if (err) {
        console.log(err)
    } else if(stdout) {
        console.log(stdout)
    } else {
        console.log(stderr)
    }
})
exec_process.stdout.on('data',(data) => console.log(`${data}`))

// spawn 接口没有回调函数,只能利用返回的ChildProcess实例绑定监听数据函数拿到子进程的输出
const spawn_process = spawn('node',['child_process'], {})
spawn_process.stdout.on('data', (data) => console.log(`${data}`))

//fork 也可以利用返回的ChildProcess实例,注意配置项silent要设为true
const fork_process = fork('child_process.js', [], {'silent': true})
fork_process.stdout.on('data' ,(data) => console.log(`${data}`))    

其次, fork返回的ChildProcess实例有一个额外的内置的通信通道IPC,它允许消息在父进程和子进程之间来回传递。

// child_process.js
process.on('message', (data) => {
  console.log(`message from Parent: ${data}`);
})
setTimeout(() => {
  process.send('send from child');
}, 2000)

// parent.js
const { fork } = require("child_process")
const p = fork(
  'child_process.js', // 需要执行的脚本路径
  [], // 传递的参数
  {}
)
p.on('message', data => {                      //监听子进程消息
  console.log(`message from child: ${data}`)
})
p.send('send from parent')                     //发送消息给子进程

集群:Cluster 模块

cluster模块是对child_process模块的进一步封装,专用于解决单进程NodeJS Web服务器无法充分利用多核CPU的问题。使用该模块可以简化多进程服务器程序的开发,让每个核上运行一个工作进程,并统一通过主进程监听端口和分发请求。 ——七天学会node.js

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
console.log(numCPUs)

if (cluster.isMaster) {
  console.log(`主进程 ${process.pid} 正在运行`);

  // 衍生工作进程。
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();      // 调用了 child_process.fork()方法创建工作进程
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`工作进程 ${worker.process.pid} 已退出`);
  });
} else {
  // 工作进程可以共享任何 TCP 连接。
  // 在本例子中,共享的是一个 HTTP 服务器。
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('你好世界\n');
  }).listen(8000);

  console.log(`工作进程 ${process.pid} 已启动`);
}
console.log("WOW")

// 输出(Mac OS)
4
主进程 55570 正在运行
WOW
4
工作进程 55571 已启动
WOW
4
工作进程 55572 已启动
WOW
4
工作进程 55573 已启动
WOW
4
工作进程 55574 已启动
WOW

//可以看到 fork 是异步创建的,调用时请求创建进程并立即返回,系统创建好进程后会加入到事件队列,执行到事件就调用回调函数,这个回调函数包括执行一遍这个文件

cluster.fork()实际调用了child_process.fork(),因此建立了IPC通道与父进程通信。它会创建一个进程并返回cluster.worker实例。创建的每个进程之间都是独立的,一个进程的开启和关闭不影响其他进程。只要有存活的进程,服务器就可以继续处理连接。

主进程负责监听端口,接收新连接后会自动将连接循环(默认)分发给cluster.fork()创建的工作进程来帮忙处理,因此可以使用cluster模块来实现简单的负载均衡。

注意

  • cluster.fork()返回cluster.worker实例可能会引起困惑,困惑的原因把主进程和工作进程做了master和worker的区分,这里不用这么区分,既然主进程也是进程,那么也可以看作worker,调用cluster.fork().send(message)就可以向子进程发送信息,同样监听信息也是cluster.fork().on('message', (data) => {...})
  • worker 和 process 都是相对的,如果当前执行进程是主进程就代表主进程,是工作进程就代表工作进程。
  • cluster代表整个集群,也就是主进程和工作进程,随着当前执行进程的变化,cluster的属性也在变化。在cluster上面绑定的事件对每个进程都起作用。cluster有一些API 是只对于主进程或只对于工作进程的,例如只能在主进程而不能在工作进程中调用的:fork、cluster.workers等,只能在工作进程不能在主进程调用的:cluster.worker等。
  • 跟上面child_process模块不一样的是,cluster.fork()只有增加进程环境变量的参数(一般是不带的),没有要执行文件路径的参数,因此像上面代码那样主进程做的事和工作进程做的事写在同一文件(if-else语句里)是合理的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • Node.js是目前非常火热的技术,但是它的诞生经历却很奇特。 众所周知,在Netscape设计出JavaScri...
    w_zhuan阅读 3,607评论 2 41
  • # 模块机制 node采用模块化结构,按照CommonJS规范定义和使用模块,模块与文件是一一对应关系,即加载一个...
    RichRand阅读 2,482评论 0 3
  • Node.js是目前非常火热的技术,但是它的诞生经历却很奇特。 众所周知,在Netscape设计出JavaScri...
    Myselfyan阅读 4,061评论 2 58
  • https://nodejs.org/api/documentation.html 工具模块 Assert 测试 ...
    KeKeMars阅读 6,297评论 0 6
  • 一张机,瓦间种子试寒泥。居身也欲达春计。清风桃杏,桑溪童子,半坐浣麻衣。 两张机,浮花缱绻几层迷。胡蜂小雀厮相喜。...
    丫头317阅读 123评论 0 1