首页 > 编程语言 >高并发下的Node.js与负载均衡

高并发下的Node.js与负载均衡

时间:2023-06-04 19:02:53浏览次数:61  
标签:Node socket process require server 并发 进程 js


 

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

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

多进程共享监听socket

Node.js与进程相关的模块有processchild_processcluster,这其中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 var cluster = require('cluster');
 2 var http = require('http');
 3 var numCPUs = require('os').cpus().length;
 4 
 5 if (cluster.isMaster) {
 6   // Fork workers.
 7   for (var i = 0; i < numCPUs; i++) {
 8     cluster.fork();
 9   }
10 
11   cluster.on('exit', function(worker, code, signal) {
12     console.log('worker ' + worker.process.pid + ' died');
13   });
14 } else {
15   // Workers can share any TCP connection
16   // In this case its a HTTP server
17   http.createServer(function(req, res) {
18     res.writeHead(200);
19     res.end("hello world\n");
20   }).listen(8000);
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是使用socketpair建立master和worker进程间的通信,并使用sendmsgrecvmsg等api来传输命令和文件描述符的。那么node.js是否支持这种方案呢?

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

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




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




child.js



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



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

master.js




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




1 var http = require('http'),
 2     cp = require('child_process'),
 3     net = require('net');
 4 var server = http.createServer(function(req,res){
 5     res.writeHead(200, {"Content-Type": "text/plain",         "Connection": "close"});
 6     res.end("hello, world");
 7 });
 8 console.log("webServer started on " + process.pid);
 9 process.on("message", function(msg,socket) {
10     process.nextTick(function(){
11         if(msg == 'c' && socket) {
12             socket.readable = socket.writable = true;
13             socket.resume();
14             server.connections++;
15             socket.server = server;
16             server.emit("connection", socket);
17             socket.emit("connect");
18         }
19     });
20 });
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上报告了我们遇到的问题(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的下一个版本一起发布。

标签:Node,socket,process,require,server,并发,进程,js
From: https://blog.51cto.com/u_2700990/6411734

相关文章

  • jsp调用数据库
    deviceInfos.jsp<%@pageimport="java.io.BufferedReader"%><%@pageimport="java.io.FileReader"%><%@pageimport="java.io.*"%><%@pageimport="java.sql.*"%><%@pagelanguage="java&qu......
  • Linux下高并发socket最大连接数所受的各种限制
    1、修改用户进程可打开文件数限制在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每个TCP连接都要创建一个socket句柄,每个socket句柄同时也是一个文件句柄)。可使用ulim......
  • nodejs调试工具
    Node应用调试工具debugger文档 http://nodejs.org/api/debugger.html内置的调试工具,支持基本的断点功能NodeInspector主页 https://github.com/node-inspector/node-inspector通过BlinkDeveloperTools提供的网页版JS调试工具来调试Node程序.NodeEclipse主页 http:......
  • uniapp中js中的闭包使用
    问题:在uniapp里面,使用闭包函数处理的时候,会导致$this不能全局使用。 解决方案:第一种代码写法(以循环为例):constobj={a:1,b:2,c:3}varkeys=Object.getOwnPropertyNames(obj)keys.forEach(function(key){console.log(key+'--......
  • node.js安装及环境配置教程【Windows系统安装包方式】
    一、下载安装包:https://nodejs.org/zh-cn/download/注:根据自己电脑系统及位数选择,我的电脑是Windows系统、64位、想下载稳定版的.msi(LTS为长期稳定版)这里选择windows64位.msi格式安装包。.msi和.zip格式区别:.msi是Windowsinstaller开发出来的程序安装文件,它可以让你安装,修改,......
  • Redis事务-秒杀并发模拟
    使用工具ab模拟测试:yuminstallhttpd-toolsab--help:使用信息ab-n1000-c100http://localhost8080/SecKill:一个1000请求中有100个并发操作vimpostfile模拟表单提交参数,以&符号结尾;存放当前目录,内容:prodid=0101&ab-n1000-c100-p~/postfile-T application/x-www......
  • SpringMVC 3使用Fastjson代替Jackson
    [size=large][color=red]Json解析教程(四.FastJson的使用)[/color][/size][url]http://zyjustin9.iteye.com/blog/2020533[/url]1.[代码][Java]代码publicclassUser{privateLongid;privateStringname;publicLonggetId(){retur......
  • Dropzone JS 使用指南(文件拖拽上传)
    JavaScript文件拖拽上传插件dropzone.js介绍[url]https://www.renfei.org/blog/dropzone-js-introduction.html[/url]DropzoneJS使用指南(文件拖拽上传)[url]http://www.open-open.com/lib/view/open1448610841329.html[/url]后台资料:基于SpringMVC的......
  • SpringMVC 转换ajax的json数据乱码问题
    在springmvc3中,已经集成了Jackson(json处理器)来处理数据输出json格式,spring中封装的类是[color=blue]org.springframework.http.converter.json.MappingJackson2HttpMessageConverter[/color]这个json转换器,如果是[color=red]springmvc3.2[/color]之前的版本,可以使用[color=b......
  • jQuery动画插件: Velocity.js
    官方:[url]http://julian.com/research/velocity/[/url]介绍:[url]http://www.w3ctech.com/topic/1403[/url]使用Velocity.js改善用户体验[url]http://www.w3ctrain.com/2015/11/15/faster-ui-animations-with-velocity-js/[/url]使用VELOCITY.JS来改善和......