一个Node.JS 的进程只会运行在单个的物理核心上,就是因为这一点,在开发可扩展的服务器的时候就需要格外的注意。

因为有一系列稳定的API,加上原生扩展的开发来管理进程,所以有很多不同的方法来设计一个可以并行的Node.JS运用。在这篇博文里,我们就来比较下这些可能的架构。

这篇文章同时也介绍compute-cluster 模块:一个小型的Node.JS库,可以用来很方便的管理进程,从来二线分布式计算。

遇到的问题

我们在Mozilla Persona的项目中需要可以处理大量不同特征的请求,所以我们尝试用使用Node.JS。

为了不影响用户体验,我们设计的‘Interactive’ 请求只需要轻量级的计算消耗,但是提供更快地反映时间使得UI没有卡壳的感觉。相比之下,‘Batch’操作大概需要半秒的处理时间,而且有可能由于其他的原因,会有更长的延迟。

为了更好的设计,我们找了很多符合我们当前需求的方法去解决。
考虑到扩展性和成本,我们列出以下关键需求:

  • 效率:能有效的使用所有空闲的处理器
  • 响应:我们的“应用”能实时快速的响应
  • 优雅:当请求量过多到不能处理的时候,我们处理我们能处理的。不能处理的要清晰的把错误反馈
  • 简单:我们的解决方案使用起来必须简单方便

通过以上几点我们可以清楚、有目标的去筛选

方案一:直接在主线程中处理.

当主线程直接处理数据的时候,结果很不好:

你不能充分利用多核CPU的优势,在交互式的请求/响应中,必须等待当前请求(或响应)处理完毕,毫无优雅可言。

这个方案唯一的优点是:够简单

1 function myRequestHandler(request, response) [
2   // Let's bring everything to a grinding halt for half a second.
3   var results = doComputationWorkSync(request.somesuch);
4 }

在 Node.JS 程序中,希望同时处理多个请求,又想同步进行处理,那你准备弄个焦头烂额吧。

方法 2: 是否使用异步处理.

如果在后台使用异步的方法来执行是否一定会有很大的性能改善呢?

答案是不一定.它取决于后台运行是否有意义

例如下面这种情况:如果在主线程上使用javascript或者本地代码进行计算时,性能并不比同步处理更好时,就不一定需要在后台用异步方法去处理

请阅读以下代码

01 function doComputationWork(input, callback) {
02   // Because the internal implementation of this asynchronous
03   // function is itself synchronously run on the main thread,
04   // you still starve the entire process.
05   var output = doComputationWorkSync(input);
06   process.nextTick(function() {
07     callback(null, output);
08   });
09 }
10  
11 function myRequestHandler(request, response) [
12   // Even though this *looks* better, we're still bringing everything
13   // to a grinding halt.
14   doComputationWork(request.somesuch, function(err, results) {
15     // ... do something with results ...
16   });
17 }

关键点就在于NodeJS异步API的使用并不依赖于多进程的应用

方案三:用线程库来实现异步处理。

只要实现得当,使用本地代码实现的库,在 NodeJS 调用的时候是可以突破限制从而实现多线程功能的。

有很多这样的例子, Nick Campbell 编写的 bcrypt library 就是其中优秀的一个。

如果你在4核机器上拿这个库来作一个测试,你将看到神奇的一幕:4倍于平时的吞吐量,并且耗尽了几乎所有的资源!但是如果你在24核机器上测试,结果将不会有太大变化:有4个核心的使用率基本达到100%,但其他的核心基本上都处于空闲状态。

问题出在这个库使用了NodeJS内部的线程池,而这个线程池并不适合用来进行此类的计算。另外,这个线程池上限写死了,最多只能运行4个线程

除了写死了上限,这个问题更深层的原因是:

  • 使用NodeJS内部线程池进行大量运算的话,会妨碍其文件或网络操作,使程序看起来响应缓慢
  • 很难找到合适的方法来处理等待队列:试想一下,如果你队列里面已经积压了5分钟计算量的线程,你还希望继续往里面添加线程吗?

内建线程机制的组件库在这种情况下并不能有效地利用多核的优势,这降低了程序的响应能力,并且随着负载的加大,程序表现越来越差

方案四:使用 NodeJS 的 cluster 模块

NodeJS 0.6.x 以上的版本提供了一个cluster模块 ,允许创建“共享同一个socket”的一组进程,用来分担负载压力。

假如你采用了上面的方案,又同时使用 cluster 模块,情况会怎样呢?

这样得出的方案将同样具有同步处理或者内建线程池一样的缺点:响应缓慢,毫无优雅可言。

有时候,仅仅添加新运行实例并不能解决问题。

方案五:引入 compute-cluster 模块

在 Persona 中,我们的解决方案是,维护一组功能单一(但各不相同)的计算进程。

在这个过程中,我们编写了 compute-cluster 库。

这个库会自动按需启动和管理子进程,这样你就可以通过代码的方式来使用一个本地子进程的集群来处理数据。

使用例子:

01 const computecluster = require('compute-cluster');
02  
03 // allocate a compute cluster
04 var cc = new computecluster({ module: './worker.js' });
05  
06 // run work in parallel
07 cc.enqueue({ input: "foo" }, function (error, result) {
08   console.log("foo done", result);
09 });
10 cc.enqueue({ input: "bar" }, function (error, result) {
11   console.log("bar done", result);
12 });

fileworker.js 中响应了 message 事件,对传入的请求进行处理:

1 process.on('message'function(m) {
2   var output;
3   // do lots of work here, and we don't care that we're blocking the
4   // main thread because this process is intended to do one thing at a time.
5   var output = doComputationWorkSync(m.input);
6   process.send(output);
7 });

无需更改调用代码,compute-cluster 模块就可以和现有的异步API整合起来,这样就能以最小的代码量换来真正的多核并行处理。

我们从四个方面来看看这个方案的表现。

多核并行能力:子进程使用了全部的核心。

响应能力:由于核心管理进程只负责启动子进程和传递消息,大部分时间里它都是空闲的,可以处理更多的交互请求。

即使机器的负载压力很大,我们仍然可以利用操作系统的调度器来提高核心管理进程的优先级。

简单性:使用了异步API来隐藏了具体实现的细节,我们可以轻易地将该模块整合到现在项目中,甚至连调用代码无需作改变。

现在我们来看看,能不能找一个方法,即使负载突然激增,系统的效率也不会异常下降。

当然,最佳目标然是,即使压力激增,系统依然能高效运行,并处理尽量多的请求。

为了帮助实现优秀的方案,compute-cluster 不仅仅只是管理子进程和传递消息,它还管理了其他信息。

它记录了当前运行的子进程数,以及每个子进程完成的平均时间。

有了这些记录,我们可以在子进程开启之前预测它大概需要多少时间。

据此,再加上用户设置的参数(max_request_time),我们可以不经过处理,直接就关闭那些可能超时的请求。

这个特性让你可以很容易根据用户体验来确定你的代码。比如说,“用户登录的时候不应该等待超过10秒。”这大概等价于将 max_request_time 设置为7秒(需要考虑网络传输时间)。

我们在对 Persona 服务进行压力测试后,得到的结果很让人满意。

在压力极高的情况下,我们依然能为已认证的用户提供服务,还阻止了一部分未认证的用户,并显示了相关的错误信息。

下一步计划

在应用层使用多子进程实现并行计算很适合单层架构的开发。这种架构只有一种功能的节点,只需添加节点就可以提高负载能力。

然而,随着项目不断变得复杂,为了性能或者安全上的因素,项目架构有可能变成多层。

除了多层架构,高可用性和扩展性也要求项目在多个地点进行部署。还有,计算密集型且要求灵活扩展的项目,可以考虑按需购买的云计算服务。

多层架构及多地点部署,及按需配置,这三点大大改变了相关参数的要求。未来版本的 compute-cluster 可能加入分布式计算及多层架构支持,以最大化利用系统资源。也可能加入基于地理信息的多地点支持,以应对各地区的不同压力。

也可能会加入硬件评价能力,以支持按需配置。

又或者,我们可能采用全新的方式来解决问题。对于怎样在保持 compute-cluster 现有特性的基础上巧妙地加入分布式支持,如果你有任何意见,请务必告诉我。

全负荷的 Node.js[转载]的更多相关文章

  1. 一名全栈工程师Node.js之路-转

    Node.js 全球现状 虽然 Node.js 在国内没有盛行,但据 StackOverflow 2016 年开发者调查,其中 node.js .全栈.JavaScript 相关的技术在多个领域(包括 ...

  2. node.js 转载:有效

    二.安装Node.js步骤 1.下载对应你系统的Node.js版本:https://nodejs.org/en/download/2.选安装目录进行安装3.环境配置4.测试 三.前期准备 1.Node ...

  3. 【译】深入理解python3.4中Asyncio库与Node.js的异步IO机制

    转载自http://xidui.github.io/2015/10/29/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3python3-4-Asyncio%E5%BA%93% ...

  4. 杂项:node.js

    ylbtech-杂项:node.js Node.js是一个Javascript运行环境(runtime),发布于2009年5月,由Ryan Dahl开发,实质是对Chrome V8引擎进行了封装.No ...

  5. 深入浅出Node.js(一):什么是Node.js

    Node.js从2009年诞生至今,已经发展了两年有余,其成长的速度有目共睹.从在github的访问量超过Rails,到去年底Node.jsS创始人Ryan Dalh加盟Joyent获得企业资助,再到 ...

  6. 最流行的Node.js应用开发框架简介

    最流行的Node.js应用开发框架简介 快速开发而又容易扩展,高性能且鲁棒性强.Node.js的出现让所有网络应用开发者的这些梦想成为现实.但是,有如其他新的开发语言技术一样,从头开始使用Node.j ...

  7. 当今最流行的Node.js应用开发框架简介

    快速开发而又容易扩展,高性能且鲁棒性强.Node.js的出现让所有网络应用开发者的这些梦想成为现实.但是,有如其他新的开发语言技术一样,从头开始使用Node.js的最基本功能来编写代码构建应用是一个非 ...

  8. .NET程序员也学Node.js——初识Node.js

    清明在石门休了八天假,一眨眼,4月又到中旬了...看到.NET在天朝彻底沦陷而又无能为力,我开始尝试去学习一些新的东西来充实自己,我自然是打死不会去学java的,没有为什么,于是乎,最近开始学习一些前 ...

  9. 深入浅出Node.js(一):什么是Node.js(转贴)

    以下内容转自:http://www.infoq.com/cn/articles/what-is-nodejs/ 作者:崔康 [编者按]:Node.js从2009年诞生至今,已经发展了两年有余,其成长的 ...

随机推荐

  1. powerdeginer 默认name 为 common

    在使用PowerDesigner对数据库进行概念模型和物理模型设计时,一般在NAME或Comment中写中文,在Code中写英文.Name用来显 示,Code在代码中使用,但Comment中的文字会保 ...

  2. EOJ 3263 丽娃河的狼人传说

    差分约束系统,$spfa$. 首先判断无解,若某个约束的$t$大于区间长度,则一定无解. 否则一定有解,可以得到一系列的不等式: 最终区间和大于等于目前的区间和:$S[R]-S[L-1]≥val$, ...

  3. snort安装--daq,dnet---ERROR! dnet header not found, go get it from...等错误解决方案

    snort源码安装过程中,需要安装daq,dnet.这里想说下如何进行安装.daq简单,源码下载直接安装就可以.dnet在安装过程中,出错后总想着在网上搜一搜,结果很失望..本篇记录的不仅仅是解决安装 ...

  4. python 三级菜单 的另1种实现方法

    menu = { "华南":{ "广东":["广州市","佛山市","深圳市","东莞市& ...

  5. js使用s:property标签接收json格式数据

    js使用s:property接收json数据时,会出现字符被转译的错误. 错误如下: 引号会被转译成'"'字符,导致解析不了. 错误原因: html的s:property接收不会出错,而js ...

  6. HDU3585 Information Disturbing 树形dp+二分

    http://acm.split.hdu.edu.cn/showproblem.php?pid=3586   题意 : 给定一个带权无向树,要切断所有叶子节点和1号节点(总根)的联系,每次切断边的费用 ...

  7. HDU 6119 小小粉丝度度熊(Two pointers)

    [题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=6119 [题目大意] 给出一些签到区间和一些补签卡,问可以创造的最长连续签到区间 [题解] 如果我们 ...

  8. lightoj 1306 - Solutions to an Equation 扩展的欧几里得

    思路:看题就知道用扩展的欧几里得算法做!!! 首先我们可以求出ax+by=gcd(a,b)=g的一个组解(x0,y0).而要使ax+by=c有解,必须有c%g==0. 继而可以得到ax+by=c的一个 ...

  9. 【8.19校内测试】【背包】【卡特兰数】【数位dp】

    早上随便搞搞t1t3就开始划水了,t2一看就是组合数学看着肚子疼...结果t1t3都a了??感天动地. 从小到大排序,从前到后枚举i,表示i是整个背包中不选的物品中代价最小的那个,即i不选,1到i-1 ...

  10. PHP文件上传学习

    PHP文件上传学习 <?php // 判断是否有文件上传 if (!isset($_FILES['upfile'])) { die('No uploaded file.'); } // 判断是否 ...