乐趣区

关于node.js:Nodejs-多进程

微信公众号:[前端一锅煮]
一点技术、一点思考。
问题或倡议,请公众号留言。

Node.js 主线程是单线程的,如果咱们应用 node app.js 形式运行,就启动了一个过程,只能在一个 CPU 中进行运算,无奈应用服务器的多核 CPU。为了解决这个问题,咱们能够应用多过程散发策略,即主过程接管所有申请,而后通过肯定的负载平衡策略散发到不同的 Node.js 子过程中。

这一实现有 2 种不同的计划:

  • 主过程监听一个端口,子过程不监听端口,主过程通过负载平衡技术散发申请到子过程;
  • 主过程和子过程别离监听不同端口,通过主过程散发申请到子过程。

第一种计划多个 Node 过程去监听同一个端口,益处是过程间通信绝对简略、缩小了端口的资源节约,然而这个时候就要保障服务过程的稳定性,特地是对 Master 过程稳定性要求会更高,编码也会简单。

第二种计划存在的问题是占用多个端口,造成资源节约,因为多个实例是独立运行的,过程间通信不太好做,益处是稳定性高,各实例之间无影响。

在 Node.js 中自带的 Cluster 模块应用的是第一种计划。

cluster 模式

cluster 模式其实就是一个主过程和多个子过程,从而造成一个集群的概念。咱们先来看看 cluster 模式的利用例子。

先实现一个简略的 app.js,代码如下:

const http = require('http');
const cluster = require('cluster');
const instances = 2; // 启动过程数量

if (cluster.isMaster) {for (let i = 0; i < instances; i++) { // 应用 cluster.fork 创立子过程
    cluster.fork();}
} else {
  // 创立 http 服务,简略返回
  const server = http.createServer((req, res) => {res.write(`hello world, start with cluster ${process.pid}`);
    res.end();});

  // 启动服务
  server.listen(8000, () => {console.log('server start http://127.0.0.1:8000');
  });
  console.log(`Worker ${process.pid} started`);
}

首先判断是否为主过程:

  • 如果是则应用 cluster.fork 创立子过程;
  • 如果不是则为子过程 require 具体的 app.js。

而后运行上面命令启动服务。

node cluster.js

启动胜利后,再关上另外一个命令行窗口,屡次运行以下命令:

curl "http://127.0.0.1:8000/"

能够看到如下输入:

hello world, start with cluster 8553
hello world, start with cluster 8552
hello world, start with cluster 8553
hello world, start with cluster 8552

前面的过程 ID 是比拟有法则的随机数,有时候输入 8553,有时候输入 8552,8553 和 8552 就是下面 fork 进去的两个子过程,上面咱们来看下为什么是这样的。

原理

首先咱们须要搞清楚两个问题:

  • Node.js 的 cluster 是如何做到多个过程监听一个端口的;
  • Node.js 是如何进行负载平衡申请散发的。

主过程判断

在 cluster 模式中存在 master 和 worker 的概念,master 是主过程,worker 是子过程。

判断主过程还是子过程的逻辑在源码中,如下:

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

通过过程环境变量设置来判断:

  • 如果没有设置则为 master 过程;
  • 如果有设置则为子过程。

因而第一次调用 cluster 模块是 master 过程,而后都是子过程。

多过程端口问题

运行下面的 app.js,胜利开启了 1 个 Master 过程、2 个 Worker 过程。

因为端口只有一个 8000,所以咱们要来看下它是由哪些过程监听的。

lsof -i:8000
node 8551 qianduanyiguozhu 23u IPv6 0xb5e3cbb6deb4d65d 0t0 TCP *:irdmi (LISTEN)

当初咱们晓得了,8000 端口它并不是被所有的过程监听,仅受到 Master 过程监听。上面让咱们再看一个信息。

ps -ef | grep 8551
501  8552  8551   0  5:53 下午 ??  0:00.10
501  8553  8551   0  5:53 下午 ??  0:00.10

这个分明展现了 Worker 与 Master 的关系,Master 通过 cluster.fork() 这个办法创立的,怎么实现过程间端口共享呢?

在后面的例子中,多个 woker 中创立的 server 监听了同个端口 8000。通常来说,多个过程监听同个端口,零碎会报错。为什么咱们的例子没问题呢?

机密在于,net 模块中,对 server.listen() 办法做了非凡解决。依据以后过程是 master 过程,还是 worker 过程:

  • master 过程:在该端口上失常监听申请。(没做非凡解决)
  • worker 过程:创立 server 实例。而后通过 send 办法,向 master 过程发送音讯,让 master 过程也创立 server 实例,并在该端口上监听申请。当申请进来时,master 过程将申请转发给 worker 过程的 server 实例。

总结就是:端口只会被主过程绑定监听一次。master 过程监听特定端口,并通过负载平衡技术将客户申请转发给各 worker 过程。

负载平衡原理

Node.js cluster 模块应用的是奴才过程形式,那么它是如何进行负载平衡解决的呢,这里次要波及两个模块。

  • round_robin_handle.js(非 Windows 平台利用模式),这是一个轮询解决模式,也就是轮询调度分发给闲暇的子过程,解决实现后回到 worker 闲暇池子中,这里要留神的就是如果绑定过就会复用该子过程,如果没有则会从新判断,这里能够通过下面的 app.js 代码来测试,用浏览器去拜访,你会发现每次调用的子过程 ID 都会不变。
  • shared_handle.js(Windows 平台利用模式),通过将文件描述符、端口等信息传递给子过程,子过程通过信息创立相应的 SocketHandle / ServerHandle,而后进行相应的端口绑定和监听、解决申请。
退出移动版