cluster原理

翻译为集群

https://www.cnblogs.com/dashnowords/p/10958457.html
https://www.cnblogs.com/dashnowords/p/11019089.html
https://yjhjstz.gitbooks.io/deep-into-node/content/chapter4/chapter4-1.html
https://zhuanlan.zhihu.com/p/157987519

背景: js是单线程, 无法充分利用服务器多核CPU的性能

  • master, 主进程, 主管监听端口
  • worker, 子进程, 由master进程fork出来的
  • slave进程,(循环)分发任务给worker进程
集群实现方案 多个node实例+多个端口 主进程向子进程转发请求(node-cluster采用)
优点 实现简单,各实例相对独立,这对服务稳定性有好处 通常只占用一个端口,通信相对简单,转发策略更灵活
缺点 增加端口占用,进程之间通信比较麻烦 实现相对复杂,对主进程的稳定性要求较高

官网例子

  1. // server.js
  2. var cluster = require('cluster');
  3. var cpuNums = require('os').cpus().length;
  4. var http = require('http');
  5. if(cluster.isMaster){
  6. console.log(cpuNums); // 12
  7. for(var i = 0; i < cpuNums; i++){
  8. cluster.fork();
  9. }
  10. }else{
  11. http.createServer(function(req, res){
  12. res.end(`response from worker ${process.pid}`);
  13. }).listen(3000);
  14. console.log(`Worker ${process.pid} started`);
  15. }
  16. // 12
  17. // Worker 68366 started
  18. // Worker 68367 started
  19. // Worker 68365 started
  20. // 每访问 http://127.0.0.1:3000/ , 由不同的进程服务

master、worker如何通信?

master进程通过 cluster.fork() 来创建 worker进程。cluster.fork() 内部 是通过 child_process.fork() 来创建子进程。

  1. master进程、worker进程是父、子进程的关系。
  2. master进程、woker进程可以通过IPC通道进行通信。(重要)(而IPC通道内部是由libuv实现的)
  • child.send方法不仅可以发送消息,还可以发送server handle(内部实际发送的是文件描述符)
    1. // lib/internal/cluster/master.js
    2. const { fork } = require('child_process');
    3. function createWorkerProcess(id, env) {
    4. return fork(cluster.settings.exec, cluster.settings.args, {
    5. // ...
    6. uid: cluster.settings.uid
    7. });
    8. }
    image.png

多个server实例,如何实现端口共享?

master进程监听特定端口,并将客户请求转发给worker进程。 (统一由master进程监听并分发)

具体, ·net模块中,对 listen() 方法进行了特殊处理。根据当前进程是master进程,还是worker进程:

  1. master进程:在该端口上正常监听请求。(没做特殊处理)
  2. worker进程:创建server实例。然后通过IPC通道,向master进程发送消息,让master进程也创建 server 实例,并在该端口上监听请求。当请求进来时,master进程将请求转发给worker进程的server实例。

image.png

  1. // lib/net.js
  2. // ...
  3. function listen(self, address, port, addressType, backlog, fd, exclusive) {
  4. exclusive = !!exclusive;
  5. if (!cluster) cluster = require('cluster');
  6. if (cluster.isMaster || exclusive) {
  7. self._listen2(address, port, addressType, backlog, fd);
  8. return;
  9. }
  10. cluster._getServer(self, {
  11. address: address,
  12. port: port,
  13. addressType: addressType,
  14. fd: fd,
  15. flags: 0
  16. }, cb);
  17. function cb(err, handle) {
  18. // ...
  19. self._handle = handle;
  20. self._listen2(address, port, addressType, backlog, fd);
  21. }
  22. }


多个server实例,来自客户端的请求如何分发到多个worker?(转发策略)

Node.js 默认采用的策略是 round-robin 时间片轮转法

当有客户请求到达,master会轮询一遍worker列表,找到第一个空闲的worker,然后将该请求转发给该worker。

设置:
可以通过环境变量NODE_CLUSTER_SCHED_POLICY设置,也可以在cluster.setupMaster(options)时传入

  1. // lib/cluster.js
  2. RoundRobinHandle.prototype.distribute = function(err, handle) {
  3. this.handles.push(handle);
  4. var worker = this.free.shift();
  5. if (worker) this.handoff(worker);
  6. };

源码解析

https://github.com/nodejs/node/blob/v14.x/lib/cluster.js
https://github.com/nodejs/node/tree/v14.x/lib/internal/cluster

流程图

image.png