nodejs事件循环与多进程(五)——cluster多进程模型 & worker进程使用fork()函数,实现与master进程间通信 & 惊群之发生多线程多进程等待同一个socket事件

nodejs事件循环与多进程(五)——cluster多进程模型 & worker进程使用fork()函数,实现与master进程间通信 & 惊群之发生多线程/多进程等待同一个socket事件

cluster

node进行多进程的模块

属性和方法

  1. isMaster 属性,返回该进程是不是主进程
  2. isWorker 属性,返回该进程是不是工作进程
  3. fork() 方法,只能通过主进程调用,衍生出一个新的 worker 进程,返回一个 worker 对象。和process.child的区别,不用创建一个新的child.js
  4. setupMaster([settings]) 方法,用于修改 fork() 默认行为,一旦调用,将会按照cluster.settings进行设置。
  5. settings 属性,用于配置,参数 exec: worker文件路径;args: 传递给 worker 的参数;execArgv: 传递给 Node.js 可执行文件的参数列表

事件

  1. fork 事件,当新的工作进程被 fork 时触发,可以用来记录工作进程活动
  2. listening 事件,当一个工作进程调用 listen() 后触发,事件处理器两个参数 worker:工作进程对象
  3. message事件, 比较特殊需要去在单独的worker上监听。
  4. online 事件,复制好一个工作进程后,工作进程主动发送一条 online 消息给主进程,主进程收到消息后触发,回调参数 worker 对象
  5. disconnect 事件,主进程和工作进程之间 IPC 通道断开后触发
  6. exit 事件,有工作进程退出时触发,回调参数 worker 对象、code 退出码、signal 进程被 kill 时的信号
  7. setup 事件,cluster.setupMaster() 执行后触发

文档地址:

https://nodejs.org/api/child_process.html 多看文档!

cluster多进程模型

每个worker进程通过使用child_process.fork()函数,基于IPC(Inter-Process Communication,进程间通信),实现与master进程间通信。

那我们直接用child_process.fork()自己实现不就行了,干嘛需要cluster呢?

这样的方式仅仅实现了多进程。多进程运行还涉及父子进程通信,子进程管理,以及负载均衡等问题,这些特性cluster帮你实现了。

在这里插入图片描述

实例

文件1

cluster\child.js

console.log('我是子进程')

文件2

cluster\main.js

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

if (cluster.isMaster) {
    
    
    for (var i = 0;i < cpuNums; i++) {
    
    
        cluster.fork('./child.js')
    }

    // 当新的工作进程被fork时,cluster模块将触发'fork'事件。 可以被用来记录工作进程活动,产生一个自定义的timeout。
    // cluster.on('fork', (worker) => {
    
    
    //     console.log('主进程fork了一个worker pid为', worker.process.pid);
    // })
    // 当一个工作进程调用listen()后,工作进程上的server会触发'listening' 事件,同时主进程上的 cluster 也会被触发'listening'事件。
    //事件处理器使用两个参数来执行,其中worker包含了工作进程对象, address 包含了以下连接属性: address、 port 和 addressType。当工作进程同时监听多个地址时,这些参数非常有用。

    // cluster.on('listening', (worker) => {
    
    
    //     console.log('主进程fork了一个worker进行http服务 pid为', worker.process.pid);
    // })

    // 当cluster主进程接收任意工作进程发送的消息后被触发
    // cluster.on('message', (data) => {
    
    
    //     console.log('收到data', data)
    // })
    // var log = cluster;

    // Object.keys(cluster.workers).forEach((id) => {
    
    
    //     cluster.workers[id].on('message', function(data) { 
    //         console.log(data)
    //     })
    // });

    cluster.on('disconnect', (worker)=> {
    
    
        console.log('有工作进程退出了', worker.process.pid )
        cluster.fork(); // 保证你永远有8个worker
    });
} else {
    
    
    // http.createServer((req, res) => {
    
    
    //     try{
    
    
    //         res.end(aasdasd); // 报错,整个线程挂掉,不能提供服务
    //     } catch(err) {
    
    
    //         process.disconnect(); // 断开连接, 断开和master的连接,守护进程其实就是重启
    //     }
    // }).listen(8001, () => {
    
    
    //   console.log('server is listening: ' + 8001);
    // });
    // console.log('xxx')
    process.send(process.pid);
    // process.disconnect();
}

打开终端,执行命令

node .\main.js

main文件的console内容都能打印

最初的多进程模型

最初的 Node.js 多进程模型就是这样实现的,master 进程创建 socket,绑定到某个地址以及端口后,自身不调用 listen 来监听连接以及 accept 连接,而是将该 socket 的 fd 传递到 fork 出来的 worker 进程,worker 接收到 fd 后再调用 listen,accept 新的连接。但实际一个新到来的连接最终只能被某一个 worker 进程 accpet 再做处理,至于是哪个 worker 能够 accept 到,开发者完全无法预知以及干预。这势必就导致了当一个新连接到来时,多个 worker 进程会产生竞争,最终由胜出的 worker 获取连接。

在这里插入图片描述

相信到这里大家也应该知道这种多进程模型比较明显的问题了

  • 多个进程之间会竞争 accpet 一个连接,产生惊群现象,效率比较低。
  • 由于无法控制一个新的连接由哪个进程来处理,必然导致各 worker 进程之间的负载非常不均衡。

这其实就是著名的”惊群”现象。

简单说来,多线程/多进程等待同一个 socket 事件,当这个事件发生时,这些线程/进程被同时唤醒,就是惊群。可以想见,效率很低下,许多进程被内核重新调度唤醒,同时去响应这一个事件,当然只有一个进程能处理事件成功,其他的进程在处理该事件失败后重新休眠(也有其他选择)。这种性能浪费现象就是惊群。

惊群通常发生在 server 上,当父进程绑定一个端口监听 socket,然后 fork 出多个子进程,子进程们开始循环处理(比如 accept)这个 socket。每当用户发起一个 TCP 连接时,多个子进程同时被唤醒,然后其中一个子进程 accept 新连接成功,余者皆失败,重新休眠。

http.Server继承了net.Server, http客户端与http服务端的通信均依赖于socket(net.Socket)。

cluster\master.js

const net = require('net');
const fork = require('child_process').fork;

var handle = net._createServerHandle('0.0.0.0', 3000);

for(var i=0;i<4;i++) {
    
    
    console.log('11111111111111111111111111')
   fork('./worker').send({
    
    }, handle);
}

cluster\worker.js

const net = require('net');
process.on('message', function(m, handle) {
    
      //master接收客户端的请求,worker去响应
  start(handle);
});

var buf = 'hello nodejs';
var res = ['HTTP/1.1 200 OK','content-length:'+buf.length].join('\r\n')+'\r\n\r\n'+buf;

var data = {
    
    };

function start(server) {
    
     // 响应逻辑,重点关注惊群的效果、计数
    server.listen();
    server.onconnection = function(err,handle) {
    
    
        var pid = process.pid;
        if (!data[pid]) {
    
    
            data[pid] = 0;
        }
        data[pid] ++;  //每次服务 +1
        console.log('got a connection on worker, pid = %d', process.pid, data[pid]);
        var socket = new net.Socket({
    
    
            handle: handle
        });
        socket.readable = socket.writable = true;
        socket.end(res);
    }
}

打开终端,执行命令

node .\master.js

worker.js文件的console内容都能打印

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44867717/article/details/131546465