[Nodejs principle source & Appreciation (4)] depth analysis of cluster module source code and node.js multiple processes (on)

Sample code is hosted: http://www.github.com/dashnowords/blogs

Blog Park address: "the history of living in the big front-end" original blog directory

Huawei cloud community Address: [you want the front Daguai upgrade guide]

I. Overview

clusterModule is a node.jsmodule for implementing and managing multiple processes. Conventional node.jsapplications are single-threaded single-process, which means that it is difficult to take full advantage of multi-core CPU performance of the server, and the clustermodule is to solve this problem, it makes the node.jsprogram can run multiple instances of the coexistence of different ways process, in order to extract greater performance of the server. node.jsUse the official sample code workerexamples to indicate the main process fork child process, so that front-end developers very easy and browser environments in the learning process workermultithreading confusion. In order to easily distinguish between us and nodethe official documents use the same name, with the cluster masterand workerto distinguish between the primary process and the work process, use worker_threadsto describe the work thread.

node.jsThe master-slave model, masterthe main process corresponds to a contractor in charge of the port monitor, the slaveprocess is used for the actual task execution, when a task request arrives, it will be distributed according to a certain connection mode loop workerprocess to handle. Theoretically, if the current individual workerto choose the work process load condition or process-related information, efficiency should be higher than the direct payment cycle, but node.jsthe document declared in this way due to the impact of operating system scheduling mechanism, will make the distribution become unstable, it will be "round-robin" as the default distribution policy.

About clusterusage and modules API details, you can refer directly to the official documentation "Node.js Chinese net V10.15.3 / Cluster" .

II. Threads and processes

Want possible use of server performance, you first need to understand the "thread" of these two concepts (thread) and "process" (process).

The computer is used to perform computing tasks from the CPU, if you only have one CPU, then this machine all the tasks it will be executed. It can be executed one after another to perform a series of tasks in accordance with the principles may be based on the principle of synchronization to perform multiple tasks in parallel, perform multiple tasks synchronization, CPU will quickly switch among multiple threads, and thread switching to switch context corresponding to the task, which will result in additional CPU resource consumption, very long time so when the number of threads, thread switching itself will waste a lot of CPU resources. If while performing a task, CPU and memory are still plenty of surplus, you can let them go some way to perform other tasks.

You can "thread" as a lightweight "process."

If you open the Task Manager in the operating system, in 进程case the label you can see an example in the following figure:

We can see every program at least open up a new process (you could instantly understand the reasons for the high efficiency of chrome, I said nothing), it is a greater granularity of resource isolation unit, used between different processes the memory area can not share data directly, only to communicate via inter-process communication mechanism, and because you want to use the new memory area, its creation and destruction are switched relatively more time consuming, it is among the benefits is the process isolated from each other, independently of each other, so you can listen to music and play games, music and software will not suddenly put a light music, the results of your game character attack power halved.

Then look at 性能the label:

You can see the number of threads is much larger than the number of processes. "Thread" is often used to improve the utilization of CPU in a single "process", which is a more granular resource scheduling unit, it is easier to create and destroy threads in the same process share allocated to this process memory, so it realize the sharing of data, multi-threaded programming is more complex, due to the sharing of data, if the pointer is passed between threads and then work on the same data source, you must "atomic operations" and "lock" issue to consider, it will be very easy to mess up, if you pass a copy of the data, will result in a waste of memory, additional threads are not isolated anomalies, abnormal and cause the entire process.

Contextual knowledge threads and processes related to the underlying operating system, I dabbled limited to share so much (will tell you, what I have to).

Three. Cluster module source code parsing

Long source of individual methods recommended code folding tool with the view.

3.1 start

clusterUse the module does not look complicated, the official gives an example of this:

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

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

  // 衍生工作进程。
  for (let i = 0; i < numCPUs; i++) {
    cluster.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} 已启动`);
}

3.2 Entrance

clusterAn inlet in the module /lib/cluster.js, where the code is very simple:

'use strict';
const childOrMaster = 'NODE_UNIQUE_ID' in process.env ? 'child' : 'master';
module.exports = require(`internal/cluster/${childOrMaster}`);

You can see that if the process environment objects have NODE_UNIQUE_IDthis variable, pass-through internal/cluster/child.jsoutput modules, otherwise pass-through internal/cluster/master.jsoutput modules. This is nodethe main process is performed to identify when a child process management, later in the code can be seen when the call cluster.fork( )would generate this environment variable to a form of self-growth ID when generating a child process.

3.3 Main process module master.js

首先运行node程序的肯定是主线程,那么我们从master.js这个模块开始,先用工具折叠一下代码浏览一下:

可以看到除了模块属性外,cluster模块对外暴露的方法只有下面3个,其他的都是用来完成内部功能的:

  • setupMaster(options )-修改fork时默认设置
  • fork( )-生成子进程
  • disconnect( )- 断开和所有子进程的连接

我们按照官方示例的逻辑路线来阅读代码cluster.fork( )方法定义在161-217行,一样是用折叠工具来看全貌:

可以看到cluster.fork( )执行时做了如下几件事情:

1.设置主线程参数
2.传入一个自增参数id(就是前文提到的NODE_UNIQUE_ID)和环境信息env来生成一个worker线程的process对象
3.将id和新的process对象传入Worker构造器生成新的worker进程实例
4.在子进程的process对象上添加了一些事件监听
5.在cluster.workers中以id为键添加对子进程的引用
6.返回子进程worker实例

接着看第一步setupMaster( ),在源码中50-95行,着重看81-95行:

留意一下主线程在进程层面监听的internalMessage事件非常关键,主进程监听到这个事件后,首先判断消息对象的cmd属性是否为NODE_DEBUGE_ENABLED,并以此为条件判断后续语句是否执行,后续的逻辑是遍历每一个worker进程实例,如果子进程的状态是onlinelistening就将子进程pid作为参数调用主进程的_debugProcess( )方法,否则改为在worker进程实例首次上线时调用。

process._debugProcess的定义在src/node_process_methods.cc里,看名字推测大致的意思就是为了启用对子进程的调试功能。这是一个重载方法,在windowslinux下有不同的实现。linux下的代码较短,基本可以看懂(不秀一下怎么对得住自己看1周的C++):

#ifdef __POSIX__
static void DebugProcess(const FunctionCallbackInfo<Value>& args) {
    //这里的常量参数是通过地址引用的worker.process.pid
  Environment* env = Environment::GetCurrent(args); 
    //用pid做参数获取当前激活的环境变量,这一步应该是在获取上下文

  if (args.Length() != 1) {//不合法调用时报错,没什么可说的
    return env->ThrowError("Invalid number of arguments.");
  }
  
  CHECK(args[0]->IsNumber());//检测参数
  pid_t pid = args[0].As<Integer>()->Value();
  int r = kill(pid, SIGUSR1);//发送SIGUSR1信号,终止了这个子进程

  if (r != 0) {//exit code为0时是正常退出,子进程未能正常中止时报错
    return env->ThrowErrnoException(errno, "kill");
  }
}

win32平台中对应的代码比较长,看不懂。总结一下这里就是,在没有收到cmd属性等于NODE_DEBUG_ENABLED的内部消息之前,什么都不做,如果收到这个消息,就终止所有的子进程,或者通过事件在子进程第一次处于online状态就终止它

按照执行顺序接下来是101-140行的createWorkerProcess(id,env)方法,看名字就知道是生成子进程process对象的,前半部分合并和处理环境参数,然后判断运行参数中是否包含启用--inspect功能的参数并进行一些处理,最后传入一堆参数调用了fork方法,这个方法就是child_process.fork( ),它就是用来生成子进程的,返回值就是子进程实例,你可以先简单浏览一下API【官方文档child_process.fork功能】,或者知道这里生成了子进程就好。

回到cluster.fork方法继续执行,下一步使用新生成的子进程process对象和唯一id作为参数传入Worker构造函数,生成worker实例,Worker的定义就在当前文件夹的worker.js中,它首先继承了EventEmitter的消息的发布订阅能力,然后把子进程的process对象挂在在自己的process属性上,接着为子进程添加errormessage事件的监听,最后暴露了一些更语义化的针对进程实例的管理方法(更详细的分析可以参考本系列前一篇博文)。生成了worker进程实例后,添加了对于message事件的响应,并在子进程process对象上监听进程的exit,disconnect,internalMessage事件,最后将worker实例和自己的id以键值对的形式添加到cluster.workers中记录,并通过return返回给外界,至此master模块的初始化流程就告一段落,先mark一下,后面还会讲这里。

3.4 子进程模块child.js

子进程模块是从master.js调用child_process时启动的,它和主进程是并行执行的。老规矩,代码折叠看一下:

看出什么了吗?child.js的代码里只有引用和定义,_setupWorker是在nodejs工作进程初始化时执行的,它在自己的独立进程中初始化了一个进程管理实例,并执行了下述逻辑:

1.实例化进程管理对象worker
2.全局添加`disconnect`事件响应
3.全局添加`internalMessage`事件响应,主要是分发`act:newconn`和`act:disconnect`事件
4.用send方法发送`online`事件,通知主线程自己已上线。

注意,这个process对象就是IPC(Inter Process Communication,也称为跨进程通讯)能够实现的关键,很明显它继承了EventEmitter的消息收发能力,在子进程内部进行消息收发不存在任何问题,还记得master.jsfork方法吗?这个process就是调用child_process启动子进程时返回给主进程的那个process对象,当你在主进程中获取它后,就可以共享worker进程的消息能力,从而在资源隔离的条件下实现masterworker进程的跨进程通讯。_getServer( )方法是在建立server实例时调用的,等到驱动事件信息到达child.js时再看,可以留意一下最后两个添加在Worker原型方法上的方法,它们只在子进程中有效。

四. 小结

至此,你已经看到node是如何通过cluster模块实现多实例并初始化跨进程通讯了。但是跨进程通讯的底层实现以及服务器的建立,以及如何在进程间协调网络请求的处理,还依赖于nethttp的一些内容,只好等研究完了再继续,硬刚反正我是吃不消的。

Guess you like

Origin www.cnblogs.com/dashnowords/p/10958457.html