一个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 的 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 服务进行压力测试后,得到的结果很让人满意。
在压力极高的情况下,我们依然能为已认证的用户提供服务,还阻止了一部分未认证的用户,并显示了相关的错误信息。
本文向大家介绍TypeScript开发Node.js程序的方法,包括了TypeScript开发Node.js程序的方法的使用技巧和注意事项,需要的朋友参考一下 当我第一次发现 TypeScript 时,就把它用到了自己的 JavaScript 程序中。使用 TypeScript 有很多好处,现在你要让我在用原生 JavaScript 写任何东西的话,需要给我一个令人信服的理由。 在本文中,我将向你
我正在学习著名的《关于Java》一书。这本书说:“如果一个线程写入一个变量,而其他线程只读取它,那么你可以让该变量保持易失性。”我不明白为什么不需要“易失性”来确保将线程刷新变量写回内存?如果没有易失性,其他线程是否会因为本地缓存而读取脏数据?
本文向大家介绍jQuery编程中的一些核心方法简介,包括了jQuery编程中的一些核心方法简介的使用技巧和注意事项,需要的朋友参考一下 调用 jQuery 对象的方法很简单: 大多数 jQuery 方法都是像上面这样被调用的,这些方法都位于 $.fn 命名空间内,这些方法称为 jQuery 对象方法。 但是也有一些方法不需要依赖于选择器的结果集,这些方法位于 jQuery 命名空间内,这些方法称为
问题内容: 我有一个缓存类,其中包含一个存储缓存项。 我很好奇更改为会带来什么后果? 我会提高性能吗?此缓存是只读缓存。 最佳选择是什么?只是HashMap?缓存将按一定间隔进行填充。 问题答案: 首先,您似乎不了解关键字的作用。它确保如果声明的变量保留的 引用值发生更改,则其他线程将看到它,而不是拥有缓存的副本。它与访问线程安全无关。 鉴于此,并且您说的是只读事实,您当然不需要使用任何提供线程安
问题内容: 我想运行我的Java应用程序,并且在给定的工作量下能够看到: 给定函数被调用了多少次 每个函数调用相对而言的成本(即每个函数执行需要多长时间) 我大致知道瓶颈在我的应用程序中的哪个位置,但是我需要更细粒度的视图才能缩小范围。 谢谢 编辑 jvisualvm就像该工具一样-它在大约30秒内发现了问题。我只需要了解方法概要文件中的“自用时间”是什么意思。谢谢 问题答案: 从Sun在Java
本文向大家介绍50个PHP程序性能优化的方法,包括了50个PHP程序性能优化的方法的使用技巧和注意事项,需要的朋友参考一下 1、 用单引号代替双引号来包含字符串,这样做会更快一些。因为 PHP 会在双引号包围的 字符串中搜寻变量,单引号则不会,注意:只有 echo 能这么做,它是一种可以把多个字符 串当作参数的“函数”(译注:PHP 手册中说 echo 是语言结构,不是真正的函数,故把函数 加上了