一个js程序将运行在一个单一的物理核心。这就是我们在开发可伸缩服务器时需要特别注意的原因。

因为有一系列稳定的API和本地扩展的开发管理过程中,有许多不同的方法来设计一个平行的Node.js应用。在这里,我们比较这些可能的框架。

本文还介绍了计算集群模块:一个小的js库,可以很方便的管理过程中,始终有两线分布式计算。

遇到的问题

我们需要一批能够处理大量在Mozilla的角色项目不同特征的要求,所以我们尝试使用Node.js。

为了不影响用户体验,我们设计了interactive'request计算需要轻消费,但提供了更快的响应时间使UI不感觉困。相反,这batch'operation大约需要半秒钟,可能有更长的延迟由于其它原因。



为了更好的设计,我们找到了许多满足我们当前需求的方法。

考虑到可伸缩性和成本,我们列出以下关键需求:

效率:所有空闲处理器的有效使用
响应:我们的应用程序可以实时快速响应。
格瑞丝:当请求量太多无法处理时,我们处理我们能处理的事情。不能处理错误的反馈。
简单:我们的解决方案必须简单易用。


通过以上几点,我们可以做出明确的有针对性的筛选。



方案1:直接在主线程中进行处理。

当主线程直接处理数据时,结果非常糟糕:

您不能充分利用多核CPU的优点。在交互式请求响应中,必须等待当前的请求(或响应)进行处理,而不需要优雅。

这个方案的唯一优点是它足够简单。






功能myrequesthandler(请求、响应){
让我们把一切都停顿半秒钟吧。
VaR结果= docomputationworksync(请求。诸如此类的东西);
}



在Node.js程序,要同时处理多个请求,并且想过程同步,所以你会很麻烦。

方法2:使用异步处理。

如果在后台使用异步方法,那么会有很多性能改进吗

答案不一定。这取决于后台操作的重要性。

例如,下面的情况是:如果在主线程上计算Javascript或本地代码,性能不比同步处理好,因此不需要在后台异步处理。

请阅读以下代码






功能docomputationwork(输入、回调){
因为此异步的内部实现
函数本身是在主线程上同步运行的,
仍然使整个过程挨饿。 /您
无功输出= docomputationworksync(输入);
process.nexttick(函数(){()
回调(空,输出);
});
}

功能myrequesthandler(请求、响应){
虽然这看起来更好,但我们仍然带来一切。
磨停。
docomputationwork(request.somesuch,功能(呃,结果){
用结果做某事…
});



}

关键的一点是,Nodejs异步API的使用不依赖于多进程的应用程序

方案三:异步处理是通过线程库实现的。

只要实施得当,图书馆是使用本地代码可以突破的限制,实现多线程功能当Nodejs叫做。

有很多这样的例子,和Nick Campbell写的BCrypt的图书馆是一个最好的。

如果你把这个库放在4核机器上进行测试,你会看到一个神奇的场景:4倍于通常的吞吐量,并且消耗几乎所有的资源。但是如果你在24核心机器上测试,结果不会改变太多:4个核心的使用率基本上是100%,但是核心的其余部分基本上是空闲的。

问题是,在Nodejs线程池中使用的库和线程池不适合这样计算。此外,线程池的上限是书面的,最多只能运行4个线程。

除了死亡上限之外,这个问题的深层原因是:

使用NodeJS内部线程池做了很多工作,它可以防止文件或网络操作,让程序看起来慢。
很难找到处理等待队列的正确方法:让我们考虑一下。如果您已经在队列中积累了5分钟的线程,是否仍要向它添加线程


在这种情况下,内置线程机制的构件库不能有效地利用多核的优势,降低了程序的响应速度,随着负载的增加,程序的性能越来越差。



方案四:使用Nodejs集群模块

0.6版的Nodejs。x提供了一种集群模块,允许一个进程组共享同一插座的负载分担压力的创作。

如果您使用上述方案并同时使用集群模块,该怎么办

该方案也有与同步处理或内置线程池相同的缺点:响应慢,不优雅。

有时,仅仅添加一个新的运行实例并不能解决问题。



方案五:引入计算集群模块

在人物角色中,我们的解决方案是维护一组单一的(但不同的)计算过程。

在这个过程中,我们编写了计算集群库。

该库将自动启动和管理过程的需求,所以,你可以使用一个集群区域的子过程的代码处理数据。

使用的例子:






const computecluster =需要('compute-cluster);

一个计算集群
VaR CC =新computecluster({模块:'。 /工人。JS的});

并行工作
Cc.enqueue({输入:foo},功能(错误,结果){
console.log(foo做
});
Cc.enqueue({输入:酒吧},功能(错误,结果){
console.log(做酒吧
});



在fileworker.js事件消息,传入的请求处理:






process.on(消息功能(M){
无功输出;
这里有很多工作,我们不在乎我们阻塞了。
主线程,因为这个过程是一次只做一件事。
无功输出= docomputationworksync(m.input);
process.send(输出);
});




在不改变调用代码的情况下,计算集群模块可以与现有的异步API集成,从而可以用最少的代码实现真正的多核并行处理。

我们从四个方面看这个计划的执行情况。

多核并行进程使用的所有核心。

响应能力:因为核心管理过程只负责启动子进程和发送消息,大部分时间都是空闲的,可以处理更多的交互请求。

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

简单性:使用异步API隐藏特定实现的细节,我们可以很容易地将模块集成到当前项目中,甚至可以在不改变代码的情况下调用代码。

现在让我们看看是否能找到一种方法,即使负载突然增加,系统的效率也不会异常降低。

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



为了帮助实现一个好的解决方案,计算集群不仅是管理过程和传递消息,而且管理其他信息。

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

通过这些记录,我们可以预测在子进程打开之前需要多长时间。

因此,与用户设置的参数(max_request_time),我们可以关闭可能超时请求没有处理。



这个功能让你轻松基于用户体验决定你的代码。例如,用户不应该等待超过10秒,当他们登录。这可能是相当于设置max_request_time 7秒(网络传输时需要考虑)。

在对人物角色服务进行压力测试后,结果令人满意。

在非常高的压力下,我们仍然可以通过认证的用户提供服务,同时也防止一些未经身份验证的用户,并显示相关的错误信息。