>  기사  >  웹 프론트엔드  >  Node에서 클러스터를 사용하는 방법을 단계별로 가르칩니다.

Node에서 클러스터를 사용하는 방법을 단계별로 가르칩니다.

青灯夜游
青灯夜游앞으로
2021-12-27 19:15:382198검색

이 글은 Node.js에서 클러스터에 대해 더 자세히 알아보고 클러스터 이벤트를 소개하는 글입니다. 모두에게 도움이 되기를 바랍니다!

Node에서 클러스터를 사용하는 방법을 단계별로 가르칩니다.

1. 소개

Node 클러스터 모듈은 멀티 코어 CPU 활용 문제를 해결하기 위해 v0.8에서 직접 도입되었으며 프로세스 견고성 문제를 처리하기 위해 비교적 완전한 API도 제공합니다.

cluster 모듈은 자식 프로세스를 생성하기 위해 포크 메서드를 호출하는데, 이는 child_process의 포크와 동일한 메서드입니다. 클러스터 모듈은 클래식 마스터-슬레이브 모델을 사용합니다. 클러스터는 마스터를 생성한 다음 지정한 수에 따라 여러 하위 프로세스를 복사합니다. Cluster.isMaster 속성을 사용하여 현재 프로세스가 마스터인지 작업자인지 확인할 수 있습니다. (작업자 프로세스). 마스터 프로세스는 모든 하위 프로세스를 관리합니다. 마스터 프로세스는 특정 작업 처리를 담당하지 않습니다. 주요 작업은 예약 및 관리를 담당하는 것입니다.

클러스터 모듈은 스레드 간의 압력을 더 잘 처리하기 위해 내장된 로드 밸런싱을 사용합니다. 로드 밸런싱은 라운드 로빈 알고리즘(라운드 로빈 알고리즘이라고도 함)을 사용합니다. 라운드 로빈 스케줄링 전략을 사용할 때 마스터는 들어오는 모든 연결 요청을 수락()한 다음 해당 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 모듈의 조합으로 적용됩니다. 클러스터가 시작되면 내부적으로 Cluster.fork() 하위 프로세스가 시작됩니다. TCP 서버 측 소켓 파일 설명자가 작업자 프로세스로 전송됩니다. 작업자 프로세스가 Cluster.fork()를 통해 복사되면 환경 변수에 NODE_UNIQUE_ID가 존재합니다. 작업자 프로세스에서 네트워크 포트를 수신하기 위한 Listen() 호출이 있으면 파일 설명자를 가져오고 SO_REUSEADDR을 전달합니다. 포트를 재사용하면 여러 하위 프로세스가 포트를 공유할 수 있습니다.

2. 클러스터 이벤트

  • fork: 이 이벤트는 작업 프로세스를 복사한 후 트리거됩니다.

  • online: 작업 프로세스를 복사한 후 적극적으로 메인 프로세스에 온라인 메시지를 보냅니다. 메인 프로세스가 메시지를 수신하면 이벤트가 트리거됩니다.

  • listening: 작업 프로세스에서 listening()을 호출한 후(서버 측 소켓이 공유됨) 청취 메시지가 메인 프로세스로 전송됩니다. 메인 프로세스가 메시지를 받은 후 이벤트가 트리거됩니다.

  • 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를 통해 자식 프로세스를 생성합니다. 포크(). 즉, 마스터 및 작업자 프로세스는 child_process에 의해 생성된 부모-자식 프로세스처럼 IPC 채널을 통해 통신하는 부모 및 자식 프로세스입니다.

IPC의 전체 이름은 프로세스 간 통신, 즉 프로세스 간 통신의 목적은 서로 다른 프로세스가 리소스에 액세스하고 서로 작업을 조정할 수 있도록 하는 것입니다. Node의 IPC 채널은 파이프 기술로 구현됩니다. 특정 구현은 libuv에서 제공됩니다. 이는 Windows에서 명명된 파이프로 구현됩니다. 애플리케이션 계층에 구현된 프로세스 간 통신에는 간단한 메시지 이벤트와 전송 방법만 있으므로 사용이 매우 간단합니다.

Node에서 클러스터를 사용하는 방법을 단계별로 가르칩니다.

실제로 하위 프로세스를 생성하기 전에 상위 프로세스는 IPC 채널을 생성하고 이를 수신합니다. 그런 다음 실제로 하위 프로세스를 생성하고 환경 변수를 통해 이 IPC 채널의 파일 설명자를 하위 프로세스에 알려줍니다. NODE_CHANNEL_FD). 시작 프로세스 동안 자식 프로세스는 파일 설명자에 따라 기존 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제