ホームページ >ウェブフロントエンド >jsチュートリアル >Node でクラスターを使用する方法を段階的に説明します。

Node でクラスターを使用する方法を段階的に説明します。

青灯夜游
青灯夜游転載
2021-12-27 19:15:382227ブラウズ

この記事では、Node.js のクラスターについて詳しく説明し、クラスター イベントについて説明します。皆様のお役に立てれば幸いです。

Node でクラスターを使用する方法を段階的に説明します。

1. はじめに

Node v0.8 では、クラスター モジュールが直接導入されました。マルチコア CPU 使用率の問題を解決し、プロセスの堅牢性に対処するための比較的完全な API も提供します。

cluster モジュールは、fork メソッドを呼び出して子プロセスを作成します。これは、child_process の fork と同じメソッドです。クラスター モジュールは、クラシックなマスター/スレーブ モデルを使用します。クラスターはマスターを作成し、指定した数に従って複数の子プロセスをコピーします。cluster.isMaster プロパティを使用して、現在のプロセスがマスターであるかワーカーであるかを判断できます。 (ワーカープロセス)。マスター プロセスはすべてのサブプロセスを管理します。マスター プロセスは特定のタスクの処理を担当しません。主な仕事はスケジュールと管理です。

クラスター モジュールは、ラウンドロビン アルゴリズム (ラウンドロビン アルゴリズムとも呼ばれます) を使用する組み込みの負荷分散を使用して、スレッド間の圧力を適切に処理します。ラウンドロビン スケジューリング戦略を使用する場合、マスターはすべての受信接続リクエストを accept() し、対応する TCP リクエスト処理を選択されたワーカー プロセスに送信します (このメソッドは引き続き IPC 経由で通信します)。 公式の使用例は次のとおりです。

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

if (cluster.isMaster) {
  for (let i = 0; i < cpuNums; i++){
    cluster.fork();
  }
  // 子进程退出监听
  cluster.on(&#39;exit&#39;, (worker,code,signal) => {
    console.log(&#39;worker process died,id&#39;,worker.process.pid)
  })
} else {
  // 给子进程标注进程名
  process.title = `cluster 子进程 ${process.pid}`;
  // Worker可以共享同一个 TCP 连接,这里是一个 http 服务器
  http.createServer((req, res)=> {
    res.end(`response from worker ${process.pid}`);
  }).listen(3000);
  console.log(`Worker ${process.pid} started`);
}

実際には、クラスタ モジュールは child_process モジュールと net モジュールの組み合わせによって適用されます。クラスタが起動すると、内部で TCP サーバーが起動されます。cluster.fork( ) 子プロセス、この TCP サーバー側ソケットのファイル記述子がワーカー プロセスに送信されます。ワーカー プロセスがcluster.fork()を介してコピーされる場合、環境変数にNODE_UNIQUE_IDが存在します。ワーカー プロセスのネットワーク ポートをリッスンするためのlisten()呼び出しがある場合、SO_REUSEADDRポートを介してファイル記述子を取得します。再利用することで、複数の子プロセスがポートを共有できるようになります。

#2. クラスター イベント

    #fork: このイベントは、ワーカー プロセスのコピー後にトリガーされます;
  • online: 作業プロセスをコピーした後、作業プロセスはメイン プロセスにオンライン メッセージをアクティブに送信します。メイン プロセスがメッセージを受信すると、イベントがトリガーされます。
  • listening:作業プロセスで呼び出されます listen() 後 (サーバー側のソケットは共有されます)、リスニング メッセージがメイン プロセスに送信され、メイン プロセスがメッセージを受信した後、イベントがトリガーされます;
  • #disconnect: メイン プロセスとワーカー プロセス このイベントは、IPC チャネルが切断されるとトリガーされます;
  • exit: このイベントは、ワーカー プロセスが終了するときにトリガーされます;
  • setup: このイベントは、cluster.setupMaster() の実行後にトリガーされます;
  • これらのイベントのほとんどは、child_process モジュールのイベントに関連しています、プロセス間メッセージ受け渡しに基づいてカプセル化されます。
  • cluster.on(&#39;fork&#39;, ()=> {
      console.log(&#39;fork 事件... &#39;);
    })
    
    cluster.on(&#39;online&#39;, ()=> {
      console.log(&#39;online 事件... &#39;);
    })
    
    cluster.on(&#39;listening&#39;, ()=> {
      console.log(&#39;listening 事件... &#39;);
    })
    
    cluster.on(&#39;disconnect&#39;, ()=> {
      console.log(&#39;disconnect 事件... &#39;);
    })
    
    cluster.on(&#39;exit&#39;, ()=> {
      console.log(&#39;exit 事件... &#39;);
    })
    
    cluster.on(&#39;setup&#39;, ()=> {
      console.log(&#39;setup 事件... &#39;);
    })

3. マスターとワーカー間の通信

上記からわかるように、マスター プロセスは、cluster.fork() を通じてワーカー プロセスを作成します。 cluster.fork() 内部 子プロセスは child_process.fork() を通じて作成されます。つまり、マスター プロセスとワーカー プロセスは親プロセスと子プロセスであり、child_process によって作成された親子プロセスと同様に、IPC チャネルを通じて通信します。

IPC の正式名は Inter-Process Communication (プロセス間通信) です。プロセス間通信の目的は、さまざまなプロセスがリソースにアクセスし、相互に作業を調整できるようにすることです。 Node の IPC チャネルはパイプ テクノロジによって実装されます。具体的な実装は libuv によって提供されます。Windows では名前付きパイプによって実装されます。*nix システムは Unix ドメイン ソケットを使用します。アプリケーション層で実装されるプロセス間通信には、単純なメッセージ イベントと送信メソッドのみがあり、非常に簡単に使用できます。

親プロセスは、実際に子プロセスを作成する前に IPC チャネルを作成してリッスンし、次に実際に子プロセスを作成し、環境変数を通じて子プロセスに通知します。 (NODE_CHANNEL_FD) この IPC チャネルのファイル記述子。起動プロセス中に、子プロセスはファイル記述子に従って既存の IPC チャネルに接続し、それによって親プロセスと子プロセス間の接続が完了します。 Node でクラスターを使用する方法を段階的に説明します。

#接続が確立されると、親プロセスと子プロセスは自由に通信できるようになります。 IPC チャネルは名前付きパイプまたはドメイン ソケットを使用して作成されるため、ネットワーク ソケットと同様に動作し、双方向通信になります。違いは、実際のネットワーク層を経由せずにシステム カーネル内でプロセス間通信を完了するため、非常に効率的であることです。 Node では、IPC チャネルは Stream オブジェクトとして抽象化されており、 send が呼び出されると、データが送信され ( write と同様)、受信したメッセージはメッセージ イベントを通じてアプリケーション層にトリガーされます ( data と同様)。

master 和 worker 进程在 server 实例的创建过程中,是通过 IPC 通道进行通信的,那会不会对我们的开发造成干扰呢?比如,收到一堆其实并不需要关心的消息?答案肯定是不会?那么是怎么做到的呢?

Node 引入进程间发送句柄的功能,send 方法除了能通过 IPC 发送数据外,还能发送句柄,第二个参数为句柄,如下所示

child.send(meeage, [sendHandle])

句柄是一种可以用来标识资源的引用,它的内部包含了指向对象的文件描述符。例如句柄可以用来标识一个服务器端 socket 对象、一个客户端 socket 对象、一个 UDP 套接字、一个管道等。 那么句柄发送跟我们直接将服务器对象发送给子进程有没有什么差别?它是否真的将服务器对象发送给子进程?

其实 send() 方法在将消息发送到 IPC 管道前,将消息组装成两个对象,一个参数是 handle,另一个是 message,message 参数如下所示

{
  cmd: &#39;NODE_HANDLE&#39;,
  type: &#39;net.Server&#39;,
  msg: message
}

发送到 IPC 管道中的实际上是要发送的句柄文件描述符,其为一个整数值。这个 message 对象在写入到 IPC 管道时会通过 JSON.stringify 进行序列化,转化为字符串。子进程通过连接 IPC 通道读取父进程发送来的消息,将字符串通过 JSON.parse 解析还原为对象后,才触发 message 事件将消息体传递给应用层使用。在这个过程中,消息对象还要被进行过滤处理,message.cmd 的值如果以 NODE_ 为前缀,它将响应一个内部事件 internalMessage ,如果 message.cmd 值为 NODE_HANDLE,它将取出 message.type 值和得到的文件描述符一起还原出一个对应的对象。这个过程的示意图如下所示

Node でクラスターを使用する方法を段階的に説明します。

在 cluster 中,以 worker 进程通知 master 进程创建 server 实例为例子。worker 伪代码如下:

// woker进程
const message = {
  cmd: &#39;NODE_CLUSTER&#39;,
  type: &#39;net.Server&#39;,
  msg: message
};
process.send(message);

master 伪代码如下:

worker.process.on(&#39;internalMessage&#39;, fn);

四、如何实现端口共享

在前面的例子中,多个 woker 中创建的 server 监听了同个端口 3000,通常来说,多个进程监听同个端口,系统会报 EADDRINUSE 异常。为什么 cluster 没问题呢?

因为独立启动的进程中,TCP 服务器端 socket 套接字的文件描述符并不相同,导致监听到相同的端口时会抛出异常。但对于 send() 发送的句柄还原出来的服务而言,它们的文件描述符是相同的,所以监听相同端口不会引起异常。

这里需要注意的是,多个应用监听相同端口时,文件描述符同一时间只能被某个进程所用,换言之就是网络请求向服务器端发送时,只有一个幸运的进程能够抢到连接,也就是说只有它能为这个请求进行服务,这些进程服务是抢占式的。

五、如何将请求分发到多个worker

  • 每当 worker 进程创建 server 实例来监听请求,都会通过 IPC 通道,在 master 上进行注册。当客户端请求到达,master 会负责将请求转发给对应的 worker;
  • 具体转发给哪个 worker?这是由转发策略决定的,可以通过环境变量 NODE_CLUSTER_SCHED_POLICY 设置,也可以在 cluster.setupMaster(options) 时传入,默认的转发策略是轮询(SCHED_RR);
  • 当有客户请求到达,master  会轮询一遍 worker 列表,找到第一个空闲的 worker,然后将该请求转发给该worker;

六、pm2 工作原理

pm2 是 node 进程管理工具,可以利用它来简化很多 node 应用管理的繁琐任务,如性能监控、自动重启、负载均衡等。

pm2 自身是基于 cluster 模块进行封装的, 本节我们主要 pm2 的 Satan 进程、God Daemon 守护进程 以及两者之间的进程间远程调用 RPC。

撒旦(Satan),主要指《圣经》中的堕天使(也称堕天使撒旦),被看作与上帝的力量相对的邪恶、黑暗之源,是God 的对立面。

Node でクラスターを使用する方法を段階的に説明します。

其中 Satan.js 提供程序的退出、杀死等方法,God.js 负责维持进程的正常运行,God 进程启动后一直运行,相当于 cluster 中的 Master进程,维持 worker 进程的正常运行。

RPC(Remote Procedure Call Protocol)是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A 服务器上,想要调用 B 服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。同一机器不同进程间的方法调用也属于 rpc 的作用范畴。 执行流程如下所示

Node でクラスターを使用する方法を段階的に説明します。

每次命令行的输入都会执行一次 satan 程序,如果 God 进程不在运行,首先需要启动 God 进程。然后根据指令,Satan 通过 rpc 调用 God 中对应的方法执行相应的逻辑。

pm2 start app.js -i 4 为例,God 在初次执行时会配置 cluster,同时监听 cluster 中的事件:

// 配置cluster
cluster.setupMaster({
  exec : path.resolve(path.dirname(module.filename), &#39;ProcessContainer.js&#39;)
});

// 监听cluster事件
(function initEngine() {
  cluster.on(&#39;online&#39;, function(clu) {
    // worker进程在执行
    God.clusters_db[clu.pm_id].status = &#39;online&#39;;
  });
  
  // 命令行中 kill pid 会触发exit事件,process.kill不会触发exit
  cluster.on(&#39;exit&#39;, function(clu, code, signal) {
    // 重启进程 如果重启次数过于频繁直接标注为stopped
    God.clusters_db[clu.pm_id].status = &#39;starting&#39;;
    // 逻辑
    // ...
  });
})();

在 God 启动后, 会建立 Satan 和 God 的rpc链接,然后调用 prepare 方法,prepare 方法会调用 cluster.fork 来完成集群的启动

God.prepare = function(opts, cb) {
  // ...
  return execute(opts, cb);
};

function execute(env, cb) {
  // ...
  var clu = cluster.fork(env);
  // ...
  God.clusters_db[id] = clu;
  
  clu.once(&#39;online&#39;, function() {
    God.clusters_db[id].status = &#39;online&#39;;
    if (cb) return cb(null, clu);
    return true;
  });
  return clu;
}

七、总结

本文从 cluster 的基本使用、事件,到 cluster 的基本实现原理,再到  pm2 如何基于 cluster 进行进程管理,带你从入门到深入原理以及了解其高阶应用,希望对你有帮助。

更多node相关知识,请访问:nodejs 教程!!

以上がNode でクラスターを使用する方法を段階的に説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。