前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >cluster模块的设计和实现

cluster模块的设计和实现

作者头像
theanarkh
发布2020-12-30 16:06:45
5910
发布2020-12-30 16:06:45
举报
文章被收录于专栏:原创分享

我们知道nodejs中实现了cluster模块,实现了服务器的多进程架构下,多个进程可以共同处理请求的能力。本文介绍如何实现一个cluster模块。

1 轮询模模式

下面我们来看一下实现。

parent.js

代码语言:javascript
复制
const childProcess = require('child_process');
const net = require('net');
const workers = [];
const workerNum = 10;
let index = 0;
// 创建多个worker进程
for (let i = 0; i < workerNum; i++) {
    workers.push(childProcess.fork('child.js', {env: {index: i}}));
}
// 主进程监听请求,轮流分发
const server = net.createServer((client) => {
    workers[index].send(null, client);
    console.log('dispatch to', index);
    index = (index + 1) % workerNum;
});
server.listen(11111);

child.js

代码语言:javascript
复制
const handle = require('../handle');
process.on('message', (message, client) => {
    console.log('receive connection from master', client);
});

主进程负责监听请求,主进程收到请求后,按照一定的算法把请求通过文件描述符的方式传给worker进程,worker进程就可以处理连接了。在分发算法这里,我们可以根据自己的需求进行自定义,比如根据当前进程的负载,正在处理的连接数。

2 共享模式

parent.js

代码语言:javascript
复制
const childProcess = require('child_process');
const net = require('net');
const workers = [];
const workerNum = 10 ;
// 绑定端口
const handle = net._createServerHandle('127.0.0.1', 11111, 4);
// 把handle传给worker进程
for (let i = 0; i < workerNum; i++) {
    const worker = childProcess.fork('child.js', {env: {index: i}});
    workers.push(worker);
    worker.send(null ,handle);
}
//  防止文件描述符泄漏
handle.close();

child.js

代码语言:javascript
复制
const net = require('net');
process.on('message', (message, handle) => {
    net.createServer(() => {
        console.log(process.env.index, 'receive connection');
    }).listen({handle});
});

我们看到主进程负责绑定端口,然后把handle传给worker进程,worker进程各自执行listen监听socket。当有连接到来的时候,操作系统会选择某一个worker进程处理该连接。我们看一下共享模式下操作系统中的架构。

实现共享模式的重点在于理解EADDRINUSE错误是怎么来的。当主进程执行bind的时候。有以下结构。

如果其他进程也执行bind并且ip和端口也一样,则操作系统会告诉我们端口已经被监听了(EADDRINUSE)。但是如果我们在子进程里不执行bind的话,就可以绕过这个限制。那么重点在于,如何在子进程中不执行bind,但是又可以绑定到同样的端口呢?有两种方式。

1 fork

我们知道fork的时候,子进程会继承主进程的文件描述符。

这时候,主进程可以执行bind和listen,然后fork子进程,最后close掉自己的fd,让所有的连接都由子进程处理就行。但是在nodejs中,我们拿不到这个fd,所以这种方式不能满足需求。

2 文件描述符传递。

nodejs的子进程是通过fork+exec模式创建的,并且nodejs文件描述符设置了close_on_exec标记,这就意味着,在nodejs中,创建子进程后,文件描述符的结构体如下(有标准输入、标准输出、标准错误三个fd)。

这时候我们可以通过文件描述符传递的方式。把方式1中拿不到的fd传给子进程。因为在nodejs中,虽然我们拿不到fd,但是我们可以拿得到fd对应的handle,我们通过ipc传输handle的时候,nodejs会为我们处理fd的问题。最后通过操作系统对传递文件描述符的处理。结构如下。

通过这种方式,我们就绕过了bind同一个端口的问题。通过以上的例子,我们知道绕过bind的问题重点在于让主进程和子进程共享socket而不是单独执行bind。实现共享的方式有两种,第一是fork,第二是文件描述符传递。对于传递文件描述符,nodejs中支持很多种方式。上面的方式是子进程各自执行listen。还有另一种模式如下

parent.js

代码语言:javascript
复制
const childProcess = require('child_process');
const net = require('net');
const workers = [];
const workerNum = 10;
const server = net.createServer(() => {
    console.log('master receive connection');
})
server.listen(11111);
for (let i = 0; i < workerNum; i++) {
    const worker = childProcess.fork('child.js', {env: {index: i}});
    workers.push(worker);
    worker.send(null, server);
}
// 防止文件描述符泄漏
server.close()

child.js

代码语言:javascript
复制
const net = require('net');
process.on('message', (message, server) => {
    server.on('connection', () => {
        console.log(process.env.index, 'receive connection');
    })
});

上面的方式中,主进程完成了bind和listen。然后把server实例传给子进程,子进程就可以监听连接的到来了。但是不管哪种模式,有一个问题需要处理的是需要关闭主进程的文件描述符,否则会造成文件描述符泄漏。

github仓库:https://github.com/theanarkh/node-cluster

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 轮询模模式
  • 2 共享模式
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档