高并发下的Node.js与负载均衡
新兴的Node.js已经吸引了很多开发人员的眼光,它提供给我们一个快速构建高性能的网络应用的平台。我也开始逐步投入node.js的怀抱,在学习和使用的过程中,遇到了一些问题,也有一些经验,我觉得有必要写出来,作为总结,也用作分享。
众所周知,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:

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进程间的通信,并使用sendmsg、recvmsg等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.js官方于1月18日发布了0.9.7(Unstable)新版本,已经包含了此改进。
高并发下的Node.js与负载均衡的更多相关文章
- 高并发下的 Nginx 优化与负载均衡
高并发下的 Nginx 优化 英文原文:Optimizing Nginx for High Traffic Loads 过去谈过一些关于Nginx的常见问题; 其中有一些是关于如何优化Nginx. ...
- Nginx+tomcat组合实现高并发场景的动静分离和负载均衡方案
简介 Java服务大多是跑在tomcat里,但是众所周知tomcat的并发性能没有优势(tomcat8及以上的版本可能有所改善),所以为了更好的适应高并发的应用场景,我们可以使用tomcat+ngin ...
- 大数据高并发系统架构实战方案(LVS负载均衡、Nginx、共享存储、海量数据、队列缓存)
课程简介: 随着互联网的发展,高并发.大数据量的网站要求越来越高.而这些高要求都是基础的技术和细节组合而成的.本课程就从实际案例出发给大家原景重现高并发架构常用技术点及详细演练. 通过该课程的学习,普 ...
- 【高可用架构】用Nginx实现负载均衡(三)
前言 在上一篇,已经用Envoy工具统一发布了Deploy项目代码.本篇我们来看看如何用nginx实现负载均衡 负载均衡器IP 192.168.10.11 [高可用架构]系列链接:待部署的架构介绍 演 ...
- MySQL读写分离高可用集群及读操作负载均衡(Centos7)
目录 概述 keepalived和heartbeat对比 一.环境 二.部署 部署lvs代理和keepalived MySQL+heartbeat+drbd的部署 MySQL主从复制 web服务器及a ...
- 高并发教程-基础篇-之nginx负载均衡的搭建
温馨提示:请不要盲目的进行横向扩展,优先考虑对单台服务器的性能优化,只有单台服务器的性能达到最优化之后,集群才会被最大的发挥作用. 一.架构图: 服务器准备:3台,ubuntu16.04系统maste ...
- 1.高并发教程-基础篇-之nginx负载均衡的搭建
温馨提示:请不要盲目的进行横向扩展,优先考虑对单台服务器的性能优化,只有单台服务器的性能达到最优化之后,集群才会被最大的发挥作用. 一.架构图: 服务器准备:3台,ubuntu16.04系统maste ...
- 【转】Node.js到底是用来做什么的
Node.js的到底是用来做什么的 在阐述之前我想放一个链接,这是国外的一个大神,对于node.js非常好的一篇介绍的文章,英文比较好的朋友可以直接去阅读,本文也很大程度上参考了这篇文章,也同时感谢知 ...
- Node.js精进(9)——性能监控(上)
市面上成熟的 Node.js 性能监控系统,监控的指标有很多. 以开源的 Easy-Monitor 为例,在系统监控一栏中,指标包括内存.CPU.GC.进程.磁盘等. 这些系统能全方位的监控着应用的一 ...
随机推荐
- SOA与WCF
背景: 高校平台马上就要进入编程阶段了,对于没怎么做过正式项目的我们来说,要学的东西实在太多了.一下子面对这么多学习资料时,我们也不能着急,还是踏踏实实,一个一个地去了解,其实他们都没那么神秘.这篇博 ...
- html & email template
html & email template inline style build tools https://templates.mailchimp.com/getting-started/h ...
- LeetCode -- Longest Increasing Subsequence(LIS)
Question: Given an unsorted array of integers, find the length of longest increasing subsequence. Fo ...
- 中英文混截,一个中文相当于n个英文
项目中遇到这么个需求,截取中英文字符串,一个中文相当于2个英文,全英文时截取12个英文字母,全中文时是6个中文汉字,中英文混合时是12个字节,在网上有找到这样的解决方案,但我没能静下心来研究懂,于是自 ...
- HDU 1159 最长公共子序列(n*m)
Common Subsequence Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Other ...
- PEP8特性
Python 的代码风格由 PEP 8 描述.这个文档描述了 Python 编程风格的方方面面.在遵守这个文档的条件下,不同程序员编写的 Python 代码可以保持最大程度的相似风格.这样就易于阅读, ...
- yii 用户登录验证(cwebuser) yii 用户登录 (记)
yii 的确是一个强大而臃肿的框架,简单的小项目,或者只做后台接口调用的项目,建议不要用. 今天记录一下yii使用中cwebuser(Yii::app()->user->login())登 ...
- qfish/Bee-Xcode-Template
https://github.com/qfish/Bee-Xcode-Template Bee-Xcode-Template Xcode Template for BeeFramework. You ...
- CppCMS1.0.3 Build by VS2012
1.CppCMS简介 CppCMS是一个C++的Web开发框架(不是一个CMS).它不同于大多数其他Web开发框架,如巨蟒Django , Java的Servlets ,或C++ Wt因为它在设计和调 ...
- FolderSize磁盘占用详情工具
FolderSize