充分发挥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 相关文章推荐
filemanage功能中用到的common.js
Apr 08 Javascript
javascript firefox兼容ie的dom方法脚本
May 18 Javascript
JAVASCRIPT函数作用域和提前声明 分享
Aug 22 Javascript
jQuery(js)获取文字宽度(显示长度)示例代码
Dec 31 Javascript
js实现点击切换TAB标签实例
Aug 21 Javascript
Javascript的无new构建实例详解
May 15 Javascript
javaScript中定义类或对象的五种方式总结
Dec 04 Javascript
详解如何构建一个Angular6的第三方npm包
Sep 07 Javascript
从源码里了解vue中的nextTick的使用
Nov 22 Javascript
微信小程序云开发实现数据添加、查询和分页
May 17 Javascript
scrapyd schedule.json setting 传入多个值问题
Aug 07 Javascript
浅析JavaScript中的事件委托机制跟深浅拷贝
Jan 20 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
模板引擎Smarty深入浅出介绍
2006/12/06 PHP
PHP遍历二维数组的代码
2011/04/22 PHP
基于curl数据采集之单页面并行采集函数get_htmls的使用
2013/04/28 PHP
shell脚本作为保证PHP脚本不挂掉的守护进程实例分享
2013/07/15 PHP
thinkphp5框架结合mysql实现微信登录和自定义分享链接与图文功能示例
2019/08/13 PHP
基于PHP实现用户在线状态检测
2020/11/10 PHP
JavaScript常用对象的方法和属性小结
2012/01/24 Javascript
javascript数字格式化通用类 accounting.js使用
2012/08/24 Javascript
javascript垃圾收集机制与内存泄漏详细解析
2013/11/11 Javascript
Javascript验证用户输入URL地址是否为空及格式是否正确
2014/10/09 Javascript
JS中FRAME的操作问题实例分析
2014/10/21 Javascript
jquery 操作css样式、位置、尺寸方法汇总
2014/11/28 Javascript
原生js实现图片轮播特效
2015/12/18 Javascript
整理一些最近经常遇到的前端面试题
2017/04/25 Javascript
教你5分钟学会用requirejs(必看篇)
2017/07/25 Javascript
vue-router实现组件间的跳转(参数传递)
2017/11/07 Javascript
vue.js项目 el-input 组件 监听回车键实现搜索功能示例
2018/08/25 Javascript
使用vue实现各类弹出框组件
2019/07/03 Javascript
vue 指令和过滤器的基本使用(品牌管理案例)
2019/11/04 Javascript
微信小程序以ssm做后台开发的实现示例
2020/04/08 Javascript
微信小程序仿通讯录功能
2020/04/09 Javascript
electron踩坑之remote of undefined的解决
2020/10/06 Javascript
python开发利器之ulipad的使用实践
2017/03/16 Python
详解python 字符串和日期之间转换 StringAndDate
2017/05/04 Python
朴素贝叶斯Python实例及解析
2018/11/19 Python
Python实现微信自动好友验证,自动回复,发送群聊链接方法
2019/02/21 Python
Python3调用百度AI识别图片中的文字功能示例【测试可用】
2019/03/13 Python
使用Python实现分别输出每个数组
2019/12/06 Python
Python基于class()实现面向对象原理详解
2020/03/26 Python
志愿者服务活动总结报告
2015/05/06 职场文书
孟佩杰观后感
2015/06/17 职场文书
《黄道婆》教学反思
2016/02/22 职场文书
导游词之江南周庄
2019/12/06 职场文书
maven依赖的version声明控制方式
2022/01/18 Java/Android
SQL SERVER实现连接与合并查询
2022/02/24 SQL Server
德劲DE1107指针试高灵敏度全波段收音机机评
2022/04/05 无线电