nodejs事件循环与多进程(五)——cluster多进程模型 & worker进程使用fork()函数,实现与master进程间通信 & 惊群之发生多线程/多进程等待同一个socket事件
cluster
node进行多进程的模块
属性和方法
- isMaster 属性,返回该进程是不是主进程
- isWorker 属性,返回该进程是不是工作进程
- fork() 方法,只能通过主进程调用,衍生出一个新的 worker 进程,返回一个 worker 对象。和process.child的区别,不用创建一个新的child.js
- setupMaster([settings]) 方法,用于修改 fork() 默认行为,一旦调用,将会按照cluster.settings进行设置。
- settings 属性,用于配置,参数 exec: worker文件路径;args: 传递给 worker 的参数;execArgv: 传递给 Node.js 可执行文件的参数列表
事件
- fork 事件,当新的工作进程被 fork 时触发,可以用来记录工作进程活动
- listening 事件,当一个工作进程调用 listen() 后触发,事件处理器两个参数 worker:工作进程对象
- message事件, 比较特殊需要去在单独的worker上监听。
- online 事件,复制好一个工作进程后,工作进程主动发送一条 online 消息给主进程,主进程收到消息后触发,回调参数 worker 对象
- disconnect 事件,主进程和工作进程之间 IPC 通道断开后触发
- exit 事件,有工作进程退出时触发,回调参数 worker 对象、code 退出码、signal 进程被 kill 时的信号
- 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('1111') 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内容都能打印
到此这篇nodejs事件循环与多进程(五)——cluster多进程模型 & worker进程使用fork()函数,实现与master进程间通信 & 惊群之发生多线程多进程等待同一个socket事件的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/hd-nodejs/10814.html