原理
入口
cluster
模块的入口在/lib/cluster.js
,这里的代码很简单:
'use strict';
const childOrMaster = 'NODE_UNIQUE_ID' in process.env ? 'child' : 'master';
module.exports = require(`internal/cluster/${childOrMaster}`);
可以看到,如果进程对象的环境变量中有NODE_UNIQUE_ID
这个变量,就透传internal/cluster/child.js
模块的输出,否则就透传internal/cluster/master.js
模块的输出。这是node
的主进程在进行子进程管理时的标识,后面的代码中可以看到当调用cluster.fork( )
生成一个子进程时会以一个自增ID的形式生成这个环境变量。
主进程模块master.js
首先运行node
程序的肯定是主线程,那么我们从master.js
这个模块开始,先用工具折叠一下代码浏览一下:
可以看到除了模块属性外,cluster
模块对外暴露的方法只有下面3个,其他的都是用来完成内部功能的:
- setupMaster(options ): 修改fork时默认设置
- fork(): 生成子进程
- disconnect(): 断开和所有子进程的连接
cluster.fork()
我们按照官方示例的逻辑路线来阅读代码cluster.fork()
方法定义在161-217行,一样是用折叠工具来看全貌:
可以看到cluster.fork()
执行时做了如下几件事情:
- 设置主线程参数
- 传入一个自增参数
id
(就是前文提到的NODE_UNIQUE_ID)和环境信息env
来生成一个worker
线程的process
对象 - 将
id
和新的process
对象传入Worker
构造器生成新的worker
进程实例 - 在子进程的
process
对象上添加了一些事件监听 - 在
cluster.workers
中以id
为键添加对子进程的引用 - 返回子进程
worker
实例
接着看第一步setupMaster()
,在源码中50-95
行,着重看81-95
行:
留意一下主线程在进程层面监听的internalMessage
事件非常关键,主进程监听到这个事件后,首先判断消息对象的cmd
属性是否为NODE_DEBUG_ENABLED
,并以此为条件判断后续语句是否执行,后续的逻辑是遍历每一个worker
进程实例,如果子进程的状态是online
或listening
就将子进程pid
作为参数调用主进程的_debugProcess( )
方法,否则改为在worker
进程实例首次上线时调用。
process._debugProcess
的定义在src/node_process_methods.cc
里,看名字推测大致的意思就是为了启用对子进程的调试功能。总结一下这里就是,在没有收到cmd
属性等于NODE_DEBUG_ENABLED
的内部消息之前,什么都不做,如果收到这个消息,就终止所有的子进程,或者通过事件在子进程第一次处于online
状态就终止它。
按照执行顺序接下来是101-140
行的createWorkerProcess(id,env)
方法,看名字就知道是生成子进程process
对象的,前半部分合并和处理环境参数,然后判断运行参数中是否包含启用--inspect
功能的参数并进行一些处理,最后传入一堆参数调用了fork
方法,这个方法就是child_process.fork( )
,它就是用来生成子进程的,返回值就是子进程实例,
回到cluster.fork
方法继续执行,下一步使用新生成的子进程process
对象和唯一id
作为参数传入Worker
构造函数,生成worker
实例,Worker
的定义就在当前文件夹的worker.js
中,它首先继承了EventEmitter
的消息的发布订阅能力,然后把子进程的process
对象挂在在自己的process
属性上,接着为子进程添加error
和message
事件的监听,最后暴露了一些更语义化的针对进程实例的管理方法(更详细的分析可以参考本系列前一篇博文)。生成了worker
进程实例后,添加了对于message
事件的响应,并在子进程process
对象上监听进程的exit,disconnect
,internalMessage
事件,最后将worker
实例和自己的id
以键值对的形式添加到cluster.workers
中记录,并通过return
返回给外界,至此master
模块的初始化流程就告一段落,先mark
一下,后面还会讲这里。
子进程模块child.js
子进程模块是从master.js
调用child_process
时启动的,它和主进程是并行执行的。老规矩,代码折叠看一下:
看出什么了吗?child.js
的代码里只有引用和定义,_setupWorker
是在nodejs
工作进程初始化时执行的,它在自己的独立进程中初始化了一个进程管理实例,并执行了下述逻辑:
- 实例化进程管理对象worker
- 全局添加
disconnect
事件响应 - 全局添加
internalMessage
事件响应,主要是分发act:newconn
和act:disconnect
事件 - 用send方法发送
online
事件,通知主线程自己已上线。
注意,这个process
对象就是IPC
(Inter Process Communication,也称为跨进程通讯)能够实现的关键,很明显它继承了EventEmitter
的消息收发能力,在子进程内部进行消息收发不存在任何问题,还记得master.js
中fork
方法吗?这个process
就是调用child_process
启动子进程时返回给主进程的那个process
对象,当你在主进程中获取它后,就可以共享worker
进程的消息能力,从而在资源隔离的条件下实现master
和worker
进程的跨进程通讯。_getServer( )
方法是在建立server
实例时调用的,等到驱动事件信息到达child.js
时再看,可以留意一下最后两个添加在Worker
原型方法上的方法,它们只在子进程中有效。