nodejs高并发架构,node.js高并发

  nodejs高并发架构,node.js高并发

  NodeJs多核多进程并行框架CNode的实现

  NodeJs多核多进程并行框架实现多核编程的重要性不用多说,我们开门见山吧。目前nodejs的网络服务器有以下几种方式支持多进程:

  #1开放多个进程,每个进程绑定不同的端口,使用Nginx等反向代理服务器进行负载均衡。好处是我们可以借助强大的Nginx做一些过滤和检查,同时可以实现更好的平衡策略,但坏处也很明显——我们引入了一个间接层。

  #2多进程绑定监听同一端口

  nodejs中提供了进程间发送“文件句柄”的功能,确实很有用(好像是雅虎工程师提交的补丁)。

  不明真相的人可以看这里:http://www.lst.de/~okir/blackhats/node121.html.

  在node中,我们可以通过下面的函数来实现这个效果:stream.write (string,encoding= UTF8 ,[FD])

  或在节点v0.5.9中的fork子流程之后:

  child.send(消息,[sendHandle])

  因此,我们设计如下方案:主进程生成监听端口后,将此listenfd发送给所有工作子进程,工作子进程收到句柄后,执行监听操作:

  主人:

  函数startWorker(句柄){

  输出( start workers: WORKER _ NUMBER );

  worker _ succ _ count=0;

  for(var I=0;I var c=CP . fork(WORKER _ PATH);

  c.send({server : true},handle);

  startServer();

  注意,因为我们只需要一个句柄,httpServer实际上是netServer的一层封装,所以我们在主进程中启动netServer,并将这个监听套接字“句柄”发送给每个子进程。

  工人:

  server=http . create server(function(req,res){

  var i,r;

  for(I=0;我10000;i ){

  r=math . random();

  res.writeHead(200,{ content-type : text/html });

  res.end(hello,world );

  child_req_count

  });

  工作进程收到句柄后,立即监听,这样就会有多个工作进程监听同一个socket端口,也就是同一个socket被添加到多个进程的epoll监控结构中。当外部连接到达时,此时只有一个幸运的工作进程获得激活事件,并接收这个连接。(UNP中提到这种情况会导致“突击群”效应。不过据江湖传言,在2.6以上的Linux系统中,奇袭团现象已经被封杀listenfd消除了,非封杀listenfd依然存在,也就是说这个问题在我们的epoll中依然会存在。不过我个人认为nodejs的epoll结构中有很多监控句柄而不仅仅是listenfd,所以此时突击组的影响应该比较小.)

  我们有五个工人测试(以下测试都是在保活模式下,本地测试):

  测试如上面的代码所示:运行Math.random () 10K次,然后输出“hello,world”;

  系统配置:

  Linux 2.6.18-164.el5xen x86_64

  CPU X5,英特尔至强处理器E5620 @ 2.40GHz

  自由-m

  缓存的已用空闲共享缓冲区总数

  记忆:7500 3672 3827 0 863 1183

  围攻-c 100 -r 1000 -b localhost:3458/

  结果是:

  交易:100000次点击

  可用性:100.00 %

  耗时:10.95秒

  传输的数据:1.05 MB

  响应时间:0.01秒

  交易速率:9132.42交易/秒

  吞吐量:0.10 MB/秒

  并发性:55.61

  成功交易:100000笔

  这五个工作线程处理的请求是:

  子请求总数:23000

  子请求总数:16000

  子请求总数:17000

  子请求总数:22000

  子请求总数:22000

  再测量一次:

  子请求总数:13000

  子请求总数:30000

  子请求总数:14000

  子请求总数:22000

  子请求总数:21000

  在这种情况下,我们的负载均衡是基于每个工作者“随机接收”的特性,这是由操作系统来保证的。在长期运行中应该是平衡的,但短期内仍可能导致负载倾斜的现象,尤其是客户端使用保活连接且长时间不关闭的情况下。

  #3一个进程负责监听和接收连接,然后将接收到的连接平均发送给子进程进行处理。

  我们来看看一个http服务器服务在正常情况下的流程,大致可以分为几个阶段:

  listenfd绑定侦听-接收到的传输控制协议连接对象 包装成窝对象 生成(请求,结果)对象-调用用户代码

  (# 1)TCP。绑定-TCP。听(过程。绑定(" TCP _ wrap "))

  TCP.emit("连接",句柄)

  (#2)将三氯苯酚句柄包装到套接字(Tcp.onconnection)

  网Server.emit(“连接",套接字)

  (#3)基于套接字创建请求、请求(net . server . connection监听)

  Http.server.emit("request ",req,res)

  (#4)您的代码写在这里:函数(请求,结果){

  res.writeHead(200," content-type/text/html ");

  res.end("你好,世界")

  }

  开发的儿童.发送(消息,[发送句柄])函数,此处的发送句柄这时应该为一个tcp_wrap对象,所以我们不能直接使用net.createServer返回给我们的插座,否则的话我们需要"回滚"从传输控制协议到窝这一步骤,不仅浪费资源,同时也是不安全的,所以我们在tcpMaster中直接使用tcp_wrap:

  var TCP=进程。绑定( TCP _ wrap ).三氯苯酚

  以上为tcpMaster进程把接收的传输控制协议连接均匀分配给三氯苯酚工作人员:

  处理上的函数(自己,处理){

  如果(自我。自身最大连接数。人脉=自我。最大连接数){

  处理。close();

  返回;

  var socket=new net .插座({

  手柄:手柄,

  allowalfopen:self。allowalfopen

  插座。可读=套接字。可写=真;

  插座。resume();

  自我联系;

  套接字.服务器=自身

  self.emit(连接,套接字);

  插座。发出(“连接”);

  服务器=http。创建服务器(函数(req,res){

  var r,I;

  for(I=0;我10000;i ){

  r=数学。random();

  res.writeHead(200,{ content-type : text/html });

  res.end(hello,world );

  子请求计数

  process.on(消息,函数(m,句柄){

  如果(句柄){

  在握(服务器,句柄);

  if(m.status==update){

  过程。发送({ status :进程。memory sage()});

  });

  以上为tcpWorker将接收到的传输控制协议句柄封装成插座,为了充分的与http.server类兼容,我们还对连接的数量进行检查,并把套接字。服务器设为当前的服务器,然后激发http.server的"连接"事件。

  通过这种方式,我们用尽量小的开销,在充分保证http.server类的兼容性的前提下,用尽量少而优雅的代码实现了负载均衡与高效并行。

  测试结果如下:

  交易:100000次点击

  可用性:100.00 %

  耗时:10.47秒

  传输的数据:1.05兆字节

  响应时间:0.01秒

  交易速率:9551.10交易/秒

  吞吐量:0.10兆字节/秒

  并发性:60.68

  成功交易:100000笔

  子请求总数:20000

  子请求总数:20000

  子请求总数:20000

  子请求总数:20000

  子请求总数:20000

  数据会有所起伏,qps总体在8000~11000 范围内,注意以上工人数目均设为5个,适量增大工人数目,qps可以稳定达到10k,但这时系统负荷比较高,使用时需谨慎选择。

  几次测试完成后,我们查看/proc/[tcpMaster]/fd,其占用的端口如下:

  0 - /dev/pts/30

  1 - /dev/pts/30

  10路插座:[71040]

  11插槽:[71044]

  12插槽:[71054]

  2 - /dev/pts/30

  3 -事件轮询:[71027]

  四管道:[71028]

  五管:[71028]

  6插槽:[71030]

  8插槽:[71032]

  9插槽:[71036]

  查看其中一个tcpWorker:

  0插槽:[71031]

  1 - /dev/pts/30

  2 - /dev/pts/30

  3 -事件轮询:[71049]

  四管道:[71050]

  五管:[71050]

  tcpMaster的固定磁碟记忆体(固定光盘商店)意义分别如下:

  一个窝为listenfd 5个窝用作父子进程通信2个管道(一对)用于异步观察器/信号观察器的触发剩余的不解释.

  tcpWorker的固定磁碟记忆体(固定光盘商店)意义分别如下:

  一个插座(这儿就是标准输入)用作与父进程通信其余软驱与掌握中软驱作用类似

  所以tcpMaster/tcpWorker端口占用正常,没有句柄泄露问题,负载均衡可控,但负责接收窝的掌握需要重新分配发送插座,引入了额外的开销。

  小结:

  介绍了两种高效的多进程运行模式,各有优缺点,需要用户自行选择。在node v0.5.10中,内置了集群库,但在我看来,其宣传意义大于实际意义,因为这样官方就可以理直气壮地宣称直接支持多进程运行模式了。为了忽悠API接口,用了一些伎俩方法,代码也比较复杂。这种多流程方法不可避免地会涉及到流程沟通和流程管理之类的事情,但是我们经常会有自己的需求。现在nodejs正式将其固化为lib,我们就不能随意更改和添加一些功能了。

  此外,还有节点、多节点和集群两个模块。所采用的策略与本文中介绍的相似,但是在使用这些模块时有一些缺点:

  更新不及时、复杂、庞大,还经常绑定很多其他功能。用户经常被绑架,遇到问题很难黑。

  基于本文的介绍,您可以轻松构建自己的高性能、易维护、最简单、优雅且实用的集群,尽情享受吧!

  源代码:https://github.com/windyrobin/iCluster

  下面这篇文章有点老,但是和这篇文章的策略很像(是我独立构思后看到的,不要喷我抄袭):

  http://developer . Yahoo . com/blogs/ydn/posts/2010/07/多核_http_server_with_nodejs/

nodejs高并发架构,node.js高并发