充分发挥Node.js程序性能的一些方法介绍


Posted in Javascript onJune 23, 2015

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

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

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

遇到的问题

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

请阅读以下代码
 

function doComputationWork(input, callback) {
 // Because the internal implementation of this asynchronous
 // function is itself synchronously run on the main thread,
 // you still starve the entire process.
 var output = doComputationWorkSync(input);
 process.nextTick(function() {
  callback(null, output);
 });
}
 
function myRequestHandler(request, response) [
 // Even though this *looks* better, we're still bringing everything
 // to a grinding halt.
 doComputationWork(request.somesuch, function(err, results) {
  // ... do something with results ...
 });

}
关键点就在于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 库。

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

使用例子:
 

const computecluster = require('compute-cluster');
 
// allocate a compute cluster
var cc = new computecluster({ module: './worker.js' });
 
// run work in parallel
cc.enqueue({ input: "foo" }, function (error, result) {
 console.log("foo done", result);
});
cc.enqueue({ input: "bar" }, function (error, result) {
 console.log("bar done", result);
});

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Javascript 相关文章推荐
基于jquery的商品展示放大镜
Aug 07 Javascript
基于PHP+Jquery制作的可编辑的表格的代码
Apr 10 Javascript
jquery移动listbox的值原理及代码
May 03 Javascript
js动态控制table的tr、td增加及删除的具体实现
Apr 30 Javascript
jquery超简单实现手风琴效果的方法
Jun 05 Javascript
js判断当前页面在移动设备还是在PC端中打开
Jan 06 Javascript
一览画面点击复选框后获取多个id值的方法
May 30 Javascript
json实现添加、遍历与删除属性的方法
Jun 17 Javascript
node.js发送邮件email的方法详解
Jan 06 Javascript
Angular 4依赖注入学习教程之简介(一)
Jun 04 Javascript
arcgis for js栅格图层叠加(Raster Layer)问题
Nov 22 Javascript
对angular4子路由&辅助路由详解
Oct 09 Javascript
Node.js编程中客户端Session的使用详解
Jun 23 #Javascript
使用Meteor配合Node.js编写实时聊天应用的范例
Jun 23 #Javascript
使用Node.js为其他程序编写扩展的基本方法
Jun 23 #Javascript
Windows系统下Node.js的简单入门教程
Jun 23 #Javascript
jQuery实现判断滚动条到底部
Jun 23 #Javascript
jQuery实现新消息在网页标题闪烁提示
Jun 23 #Javascript
使用Raygun对Node.js应用进行错误处理的方法
Jun 23 #Javascript
You might like
PHP验证码函数代码(简单实用)
2013/09/29 PHP
2014最热门的24个php类库汇总
2014/12/18 PHP
Symfony2 session用法实例分析
2016/02/04 PHP
PHP缩略图生成和图片水印制作
2017/01/07 PHP
解密效果
2006/06/23 Javascript
关于JavaScript的一些看法
2009/05/27 Javascript
在IE 浏览器中使用 jquery的fadeIn() 效果 英文字符字体加粗
2011/06/02 Javascript
JavaScript中的property和attribute介绍
2011/12/26 Javascript
js实现绿白相间竖向网页百叶窗动画切换效果
2015/03/02 Javascript
javascript中SetInterval与setTimeout的定时器用法
2015/08/24 Javascript
Jquery日历插件制作简单日历
2015/10/28 Javascript
Bootstrap模态框(modal)垂直居中的实例代码
2016/08/18 Javascript
JS 滚动事件window.onscroll与position:fixed写兼容IE6的回到顶部组件
2016/10/10 Javascript
jQuery grep()方法详解及实例代码
2016/10/30 Javascript
微信小程序 高德地图SDK详解及简单实例(源码下载)
2017/01/11 Javascript
vue 集成jTopo 处理方法
2019/08/07 Javascript
python中实现php的var_dump函数功能
2015/01/21 Python
Python中最常用的操作列表的几种方法归纳
2015/04/24 Python
python opencv之分水岭算法示例
2018/02/24 Python
TensorFlow实现AutoEncoder自编码器
2018/03/09 Python
Python 25行代码实现的RSA算法详解
2018/04/10 Python
基于pip install django失败时的解决方法
2018/06/12 Python
python人民币小写转大写辅助工具
2018/06/20 Python
对numpy Array [: ,] 的取值方法详解
2018/07/02 Python
一篇文章彻底搞懂Python中可迭代(Iterable)、迭代器(Iterator)与生成器(Generator)的概念
2019/05/13 Python
Python 运行.py文件和交互式运行代码的区别详解
2019/07/02 Python
简单了解python的内存管理机制
2019/07/08 Python
Python将文字转成语音并读出来的实例详解
2019/07/15 Python
Python QT组件库qtwidgets的使用
2020/11/02 Python
html5 touch事件实现触屏页面上下滑动(二)
2016/03/10 HTML / CSS
瑜伽服装品牌:露露柠檬(lululemon athletica)
2017/06/04 全球购物
New Balance澳大利亚官网:运动鞋和健身服装
2019/02/23 全球购物
活动总结结尾怎么写
2014/08/30 职场文书
2014年幼儿园学期工作总结
2014/12/05 职场文书
南湾猴岛导游词
2015/02/09 职场文书
《狼王梦》读后感:可怜天下父母心
2019/11/01 职场文书