利用fork实现master-worker模型
首先来看看,如果我们在child.js
中启动一个http服务会发生什么情况。
// master.js const { fork } = require('child_process') for (let i = 0; i < 2; i++) { const child = fork('./child.js') } // child.js const http = require('http') http.createServer((req, res) => { res.end('Hello World\n'); }).listen(8000)
+--------------+ | | | master | | | +--------+--------------+- -- -- - | | | Error: listen EADDRINUSE | | | +----v----+ +-----v---+ | | | | | worker1 | | worker2 | | | | | +---------+ +---------+ :8000 :8000
我们fork了两个子进程,因为两个子进程同时对一个端口进行监听,Node会直接抛出一个异常(Error: listen EADDRINUSE
),如上图所示。那么我们能不能使用代理模式,同时监听多个端口,让master进程监听80端口收到请求时,再将请求分发给不同服务,而且master进程还能做适当的负载均衡。
+--------------+ | | | master | | :80 | +--------+--------------+---------+ | | | | | | | | +----v----+ +-----v---+ | | | | | worker1 | | worker2 | | | | | +---------+ +---------+ :8000 :8001
但是这么做又会带来另一个问题,代理模式中十分消耗文件描述符(linux系统默认的最大文件描述符限制是1024),文件描述符在windows系统中称为句柄(handle),习惯性的我们也可以称linux中的文件描述符为句柄。当用户进行访问,首先连接到master进程,会消耗一个句柄,然后master进程再代理到worker进程又会消耗掉一个句柄,所以这种做法十分浪费系统资源。为了解决这个问题,Node的进程间通信可以发送句柄,节省系统资源。
句柄是一种特殊的智能指针 。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。
我们可以在master进程启动一个tcp服务,然后通过IPC将服务的句柄发送给子进程,子进程再对服务的连接事件进行监听,具体代码如下:
// master.js var { fork } = require('child_process') var server = require('net').createServer() server.on('connection', function(socket) { socket.end('handled by master') // 响应来自master }) server.listen(3000, function() { console.log('master listening on: ', 3000) }) for (var i = 0; i < 2; i++) { var child = fork('./child.js') child.send('server', server) // 发送句柄给worker console.log('worker create, pid is ', child.pid) } // child.js process.on('message', function (msg, handler) { if (msg !== 'server') { return } // 获取到句柄后,进行请求的监听 handler.on('connection', function(socket) { socket.end('handled by worker, pid is ' + process.pid) }) })
内容版权声明:除非注明,否则皆为本站原创文章。