阅读本文约需要6分钟
大家好,我是你们的导师,我每天都会在这里给大家分享一些干货内容(当然了,周末也要允许老师休息一下哈)。上次老师跟大家分享了下浅谈前端自动化构建的相关知识,今天跟大家分享浅谈前端自动化构建的相关知识参考来源:https://www.cnblogs.com/tingshuo/archive/2013/01/17/2864280.html
众所周知,node.js基于v8引擎,所以它本身并不支持多线程(有多线程的Module哦),那么为了充分利用server的Multi-core,就必须使用多进程的方式。那么进程之间如何负载均衡就会是一个关键所在。
多进程共享监听socket
Node.js与进程相关的模块有process,child_process,cluster,这其中cluster用于方便的创建共享端口的多进程模式(The cluster module allows you to easily create a network of processes that all share server ports),这种模式使多个进程间共享一个监听状态的socket,并由系统将accept的connection分配给不同的子进程,而且实现起来也非常简单,cluster为你做了大部分事情,这里有一个test case:
var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; if (cluster.isMaster) { // Fork workers. for (var i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); }); } else { // Workers can share any TCP connection // In this case its a HTTP server http.createServer(function(req, res) { res.writeHead(200); res.end("hello world\n"); }).listen(8000); }
但是这种完全依赖于系统的负载均衡存在着一个重要缺陷:在linux和Solaris上,只要某个子进程的accept queue为空(通常为最后创建的那个子进程),系统就会将多个connetion分配到同一个子进程上,这会造成进程间负载极为不均衡。
特别是在使用长连接的时候,单位时间内的new coming connection并不高,子进程的accept queue往往均为空,就会导致connection会不停的分配给同一个进程。所以这种负载均衡完全依赖于accept queue的空闲程度,只有在使用短连接,而且并发非常高的情况下,才能达到负载均衡,但是这个时候系统的load会非常高,系统也会变得不稳定起来。
Nginx是怎么做的?
如果你了解nginx,那么你可能第一时间会想到使用nginx的处理方式,nginx有一个master和多个worker进程,master进程监听端口,负责accept connection,并把accept 的socket发送给各worker进程,由worker进程接收数据并处理。
linux下,nginx是使用socketpair建立master和worker进程间的通信,并使用sendmsg、recvmsg等api来传输命令和文件描述符的。那么node.js是否支持这种方案呢?
答案是肯定的,作出这个回答的依据在于node.js的child_process和cluster模块均有一个send方法:child.send(message, [sendHandle])
这个方法的第二个参数就是我们想要传递的socket,而且node.js文档上还给出了一个test case:
var normal = require('child_process').fork('child.js', ['normal']); var special = require('child_process').fork('child.js', ['special']); // Open up the server and send sockets to child var server = require('net').createServer(); server.on('connection', function (socket) { // if this is a VIP if (socket.remoteAddress === '74.125.127.100') { special.send('socket', socket); return; } // just the usual dudes normal.send('socket', socket); }); server.listen(1337);
child.js
process.on('message', function(m, socket) { if (m === 'socket') { socket.end('You were handled as a ' + process.argv[2] + ' person'); } });
简单,精炼!似乎是一个完美的解决方案。我们稍微加工一下,让他成为一个可以正常运行的http server:
master.js
var http = require('http'), numCPUs = require('os').cpus().length; cp = require('child_process'), net = require('net'); var workers = []; for (var i = 0; i < numCPUs; i++) { workers.push(cp.fork('app.js', ['normal'])); } net.createServer(function(s) { s.pause(); var worker = worker.shift(); worker.send('c',s); workers.push(worker); }).listen(80);
var http = require('http'), cp = require('child_process'), net = require('net'); var server = http.createServer(function(req,res){ res.writeHead(200, {"Content-Type": "text/plain", "Connection": "close"}); res.end("hello, world"); }); console.log("webServer started on " + process.pid); process.on("message", function(msg,socket) { process.nextTick(function(){ if(msg == 'c' && socket) { socket.readable = socket.writable = true;socket.resume(); server.connections++; socket.server = server; server.emit("connection", socket); socket.emit("connect"); } }); });
我们在worker进程中创建了一个http server,但是这个http server并不监听,也不绑定端口,在收到master传输过来的socket时,调用server.emit("connection", socket);就可以触发server的connection事件了
看起来很不错,简单的测试之后可以正常工作,这个方案几近于完美。在经历过共享监听socket方案的失败后,我们把服务迁移到这种架构上来。
但是,我们遇到了问题。我们发现master进程的cpu和内存在逐渐增长,并最终到达100%,或者node.js崩溃(Assertion `fd_to_send >= 0' failed),这令我很抓狂,百般无奈之下我们求助于node.js的开发人员,在github上报告了我们遇到的问题(Issue #4587)。
就在当天晚上,node.js的开发人员indutny找到了问题所在,主要在于主进程在将socket发送给子进程之后,并没有销毁,而是保留在socketList中,这会导致socket在主进程中逐步累积,并最终达到上限。
indutny很快解决了这个问题,于第二天提交了这个commit,按照这个commit,indutny给send函数增加了第三个可选参数,修改后的send函数将变为:
child.send(message,[socket], [{ track: false, process: false }])
我们的master进程不需要track每个socket状态,所以我们将它设为false即可。到此,这个问题得到了完美的解决
今天就分享这么多,关于高并发下的Node.js与负载均衡,你学会了多少?欢迎在留言区评论,对于有价值的留言,我们都会一一回复的。如果觉得文章对你有一丢丢帮助,请点右下角【在看】,让更多人看到该文章。如果有想了解的,也可以进行留言