充分发挥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 相关文章推荐
javascript 写类方式之九
Jul 05 Javascript
多浏览器支持的右下角浮动窗口
Apr 01 Javascript
工作需要写的一个js拖拽组件
Jul 28 Javascript
jquery xMarquee实现文字水平无缝滚动效果
Apr 29 Javascript
JavaScript多线程详解
Aug 12 Javascript
XML文件转化成NSData对象的方法
Aug 12 Javascript
js实现小窗口拖拽效果
Dec 03 Javascript
简单理解js的冒泡排序
Dec 19 Javascript
微信小程序 登陆流程详细介绍
Jan 17 Javascript
JavaScript数组复制详解
Feb 02 Javascript
vue-cli启动本地服务局域网不能访问的原因分析
Jan 22 Javascript
Vue中使用的EventBus有生命周期
Jul 12 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使用逆波兰式计算工资的方法
2015/07/29 PHP
Yii+MYSQL锁表防止并发情况下重复数据的方法
2016/07/14 PHP
phpmyadmin在宝塔面板里进不去的解决方案
2020/07/06 PHP
JSON 客户端和服务器端的格式转换
2009/08/27 Javascript
javascript innerHTML使用分析
2010/12/03 Javascript
js动态生成指定行数的表格
2013/07/11 Javascript
2014年最火的Node.JS后端框架推荐
2014/10/27 Javascript
JavaScript 匿名函数和闭包介绍
2015/04/13 Javascript
分享12个实用的jQuery代码片段
2016/03/09 Javascript
Bootstrap中的fileinput 多图片上传及编辑功能
2016/09/05 Javascript
js输入框使用正则表达式校验输入内容的实例
2017/02/12 Javascript
详解Vue中localstorage和sessionstorage的使用
2017/12/22 Javascript
jQuery实现动态显示select下拉列表数据的方法
2018/02/05 jQuery
微信小程序之圆形进度条实现思路
2018/02/22 Javascript
微信小程序实现工作时间段选择
2019/02/15 Javascript
Vue基于vuex、axios拦截器实现loading效果及axios的安装配置
2019/04/26 Javascript
Vue+Element实现动态生成新表单并添加验证功能
2019/05/23 Javascript
JavaScript在web自动化测试中的作用示例详解
2019/08/25 Javascript
Python程序退出方式小结
2017/12/09 Python
一篇文章搞定Python操作文件与目录
2019/08/13 Python
python实现证件照换底功能
2019/08/20 Python
python基于K-means聚类算法的图像分割
2019/10/30 Python
tensorflow保持每次训练结果一致的简单实现
2020/02/17 Python
HTML5 3D旋转相册的实现示例
2019/12/03 HTML / CSS
香港零食网购:上仓胃子
2020/06/08 全球购物
SQL面试题
2013/12/09 面试题
解释一下钝化(Swap out)
2016/12/26 面试题
学院领导推荐信
2013/10/30 职场文书
机修工岗位职责
2013/11/24 职场文书
冬季安全检查方案
2014/05/23 职场文书
新教师培训方案
2014/06/08 职场文书
大学军训心得体会800字
2016/01/11 职场文书
导游词之山西祁县乔家大院
2019/10/14 职场文书
python执行js代码的方法
2021/05/13 Python
python井字棋游戏实现人机对战
2022/04/28 Python
使用CSS实现六边形的图片效果
2022/08/05 HTML / CSS