Chinaunix首页 | 论坛 | 博客
  • 博客访问: 7197824
  • 博文数量: 510
  • 博客积分: 12019
  • 博客等级: 上将
  • 技术积分: 6836
  • 用 户 组: 普通用户
  • 注册时间: 2005-08-01 16:46
文章分类

全部博文(510)

文章存档

2022年(2)

2021年(6)

2020年(59)

2019年(4)

2018年(10)

2017年(5)

2016年(2)

2015年(4)

2014年(4)

2013年(16)

2012年(47)

2011年(65)

2010年(46)

2009年(34)

2008年(52)

2007年(52)

2006年(80)

2005年(22)

分类: LINUX

2016-04-12 11:53:17

新兴的已经吸引了很多开发人员的眼光,它提供给我们一个快速构建高性能的网络应用的平台。我也开始逐步投入node.js的怀抱,在学习和使用的过程中,遇到了一些问题,也有一些经验,我觉得有必要写出来,作为总结,也用作分享。

众所周知,node.js基于引擎,所以它本身并不支持多线程(有多线程的哦),那么为了充分利用server的Multi-core,就必须使用多进程的方式。那么进程之间如何负载均衡就会是一个关键所在。
 多进程共享监听socket

Node.js与进程相关的模块有,,, 这其中cluster用于方便的创建共享端口的多进程模式(The cluster module allows you to easily create a network of processes that all share server ports),这种模式使多个进程间共享一个监听状态的socket,并由系统将accept的connection分配给不同的子进程,而且实现起来也 非常简单,cluster为你做了大部分事情,这里有一个test case:

点击(此处)折叠或打开

  1. 1 var cluster = require('cluster');
  2.  2 var http = require('http');
  3.  3 var numCPUs = require('os').cpus().length;
  4.  4
  5.  5 if (cluster.isMaster) {
  6.  6 // Fork workers.
  7.  7 for (var i = 0; i < numCPUs; i++) {
  8.  8 cluster.fork();
  9.  9 }
  10. 10
  11. 11 cluster.on('exit', function(worker, code, signal) {
  12. 12 console.log('worker ' + worker.process.pid + ' died');
  13. 13 });
  14. 14 } else {
  15. 15 // Workers can share any TCP connection
  16. 16 // In this case its a HTTP server
  17. 17 http.createServer(function(req, res) {
  18. 18 res.writeHead(200);
  19. 19 res.end("hello world\n");
  20. 20 }).listen(8000);
  21. 21 }
但是这种完全依赖于系统的负载均衡存在着一个重要缺陷:在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是使用建立master和worker进程间的通信,并使用、等api来传输命令和文件描述符的。那么node.js是否支持这种方案呢?

答案是肯定的,作出这个回答的依据在于node.js的child_process和cluster模块均有一个send方法:

这个方法的第二个参数就是我们想要传递的socket,而且node.js文档上还给出了一个test case:

点击(此处)折叠或打开

  1. 1 var normal = require('child_process').fork('child.js', ['normal']);
  2.  2 var special = require('child_process').fork('child.js', ['special']);
  3.  3 // Open up the server and send sockets to child
  4.  4 var server = require('net').createServer();
  5.  5 server.on('connection', function (socket) {
  6.  6 // if this is a VIP
  7.  7 if (socket.remoteAddress === '74.125.127.100') {
  8.  8 special.send('socket', socket);
  9.  9 return;
  10. 10 }
  11. 11 // just the usual dudes
  12. 12 normal.send('socket', socket);
  13. 13 });
  14. 14 server.listen(1337);

child.js


点击(此处)折叠或打开

  1. 1 process.on('message', function(m, socket) {
  2. 2 if (m === 'socket') {
  3. 3 socket.end('You were handled as a ' + process.argv[2] + ' person');
  4. 4 }
  5. 5 });

简单,精炼!似乎是一个完美的解决方案。我们稍微加工一下,让他成为一个可以正常运行的http server:

master.js


点击(此处)折叠或打开

  1. 1 var http = require('http'),
  2.  2 numCPUs = require('os').cpus().length;
  3.  3 cp = require('child_process'),
  4.  4 net = require('net');
  5.  5 var workers = [];
  6.  6 for (var i = 0; i < numCPUs; i++) {
  7.  7 workers.push(cp.fork('app.js', ['normal']));
  8.  8 }
  9.  9
  10. 10 net.createServer(function(s) {
  11. 11 s.pause();
  12. 12 var worker = worker.shift();
  13. 13 worker.send('c',s);
  14. 14 workers.push(worker);
  15. 15 }).listen(80);

点击(此处)折叠或打开

  1. 1 var http = require('http'),
  2.  2 cp = require('child_process'),
  3.  3 net = require('net');
  4.  4 var server = http.createServer(function(req,res){
  5.  5 res.writeHead(200, {"Content-Type": "text/plain", "Connection": "close"});
  6.  6 res.end("hello, world");
  7.  7 });
  8.  8 console.log("webServer started on " + process.pid);
  9.  9 process.on("message", function(msg,socket) {
  10. 10 process.nextTick(function(){
  11. 11 if(msg == 'c' && socket) {
  12. 12 socket.readable = socket.writable = true;
  13. 13 socket.resume();
  14. 14 server.connections++;
  15. 15 socket.server = server;
  16. 16 server.emit("connection", socket);
  17. 17 socket.emit("connect");
  18. 18 }
  19. 19 });
  20. 20 });
  21. 21

我们在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上报告了我们遇到的问题()。就在当天晚上,node.js的开发人员找到了问题所在,主要在于主进程在将socket发送给子进程之后,并没有销毁,而是保留在socketList中,这会导致socket在主进程中逐步累积,并最终达到上限。

很快解决了这个问题,于第二天提交了这个,按照这个commit,给send函数增加了第三个可选参数,修改后的send函数将变为:

child.send(message,[socket], [{ track: false, process: false }])

我们的master进程不需要track每个socket状态,所以我们将它设为false即可。到此,这个问题得到了完美的解决,希望这个改进可以随node.js的下一个版本一起发布。






阅读(2799) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~