充分发挥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 相关文章推荐
Some tips of wmi scripting in jscript (1)
Apr 03 Javascript
jQuery 全选效果实现代码
Mar 23 Javascript
[将免费进行到底]在Amazon的一年免费服务器上安装Node.JS, NPM和OurJS博客
Aug 18 Javascript
JavaScript中split与join函数的进阶使用技巧
May 03 Javascript
jquery 全选、全不选、反选效果的实现代码【推荐】
May 05 Javascript
只要1K 纯JS脚本送你一朵3D红色玫瑰
Aug 09 Javascript
用Vue.js实现监听属性的变化
Nov 17 Javascript
React利用插件和不用插件实现双向绑定的方法详解
Jul 03 Javascript
ng-events类似ionic中Events的angular全局事件
Sep 05 Javascript
Node绑定全局TraceID的实现方法
Nov 14 Javascript
js函数柯里化的方法和作用实例分析
Apr 11 Javascript
原生js实现随机点名
Jul 05 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/04/04 PHP
解析PHP中intval()等int转换时的意外异常情况
2013/06/21 PHP
php中调用其他系统http接口的方法说明
2014/02/28 PHP
PHP简单预防sql注入的方法
2016/09/27 PHP
PHP实现对图片的反色处理功能【测试可用】
2018/02/01 PHP
Laravel 5+ .env环境配置文件详解
2020/04/06 PHP
JavaScript instanceof 的使用方法示例介绍
2013/10/23 Javascript
jQuery使用before()和after()在元素前后添加内容的方法
2015/03/26 Javascript
javascript实现超炫的向上滑行菜单实例
2015/08/03 Javascript
JavaScript判断表单中多选框checkbox选中个数的方法
2015/08/17 Javascript
简单理解vue中track-by属性
2016/10/26 Javascript
浅谈jquery选择器 :first与:first-child的区别
2016/11/20 Javascript
JS中使用textPath实现线条上的文字
2017/12/25 Javascript
JS处理一些简单计算题
2018/02/24 Javascript
js jquery 获取某一元素到浏览器顶端的距离实现方法
2018/09/05 jQuery
js中怎么判断两个字符串相等的实例
2019/01/17 Javascript
小程序开发中如何使用async-await并封装公共异步请求的方法
2019/01/20 Javascript
新手快速上手webpack4打包工具的使用详解
2019/01/28 Javascript
vue中如何实现后台管理系统的权限控制的方法步骤
2019/09/05 Javascript
如何搭建一个完整的Vue3.0+ts的项目步骤
2020/10/18 Javascript
Python里disconnect UDP套接字的方法
2015/04/23 Python
Python实现句子翻译功能
2017/11/14 Python
python导入csv文件出现SyntaxError问题分析
2017/12/15 Python
Python使用matplotlib实现绘制自定义图形功能示例
2018/01/18 Python
Python使用functools实现注解同步方法
2018/02/06 Python
TensorFlow实现卷积神经网络CNN
2018/03/09 Python
使用Keras实现简单线性回归模型操作
2020/06/12 Python
使用Python制作一个数据预处理小工具(多种操作一键完成)
2021/02/07 Python
详解CSS3阴影 box-shadow的使用和技巧总结
2016/12/03 HTML / CSS
HTML5 Plus 实现手机APP拍照或相册选择图片上传功能
2016/07/13 HTML / CSS
HTML5如何为形状图上颜色怎么绘制具有颜色和透明度的矩形
2014/06/23 HTML / CSS
美国市场上最实惠的送餐服务:Dinnerly
2018/03/18 全球购物
澳大利亚儿童鞋在线:The Trybe
2019/07/16 全球购物
测试时代收集的软件测试面试题
2013/09/25 面试题
高中教师评语大全
2014/04/25 职场文书
导游词之西递宏村
2019/12/10 职场文书