利用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)
})
})
内容版权声明:除非注明,否则皆为本站原创文章。
