Node.js on multi-core machines
node.js看起来很有趣,但我一定错过了一些东西——node.js的调优是否只是为了在单个进程和线程上运行?
那么,它如何扩展到多核CPU和多CPU服务器呢?毕竟,尽可能快地生成单线程服务器非常好,但是对于高负载,我希望使用多个CPU。使应用程序更快也同样如此——今天的方法似乎是使用多个CPU并并行化任务。
node.js如何适应此图片?它的想法是以某种方式分发多个实例还是什么?
[本帖自2012年9月2日起为最新版本(高于上述版本)。]
node.js绝对可以在多核机器上进行扩展。
是的,node.js是每个进程一个线程。这是一个非常深思熟虑的设计决策,消除了处理锁语义的需要。如果您不同意这一点,您可能还没有意识到调试多线程代码有多么困难。要更深入地解释node.js过程模型以及它为什么这样工作(以及为什么它永远不支持多个线程),请阅读我的另一篇文章。
那么我如何利用我的16核盒子呢?两种方式:
- 对于像图像编码这样的大型计算任务,node.js可以启动子进程或向其他工作进程发送消息。在这个设计中,您将拥有一个线程来管理事件流和n个进程,它们执行大量的计算任务,并占用其他15个CPU。
- 为了扩展Web服务的吞吐量,您应该在一个盒子上运行多个node.js服务器,每个核心运行一个,并在它们之间分配请求流量。这提供了出色的CPU亲和力,并将通过核心计数近似线性地扩展吞吐量。
扩展Web服务的吞吐量
由于V6.0.x node.js包含了直接开箱即用的集群模块,因此可以很容易地设置多个节点工作者,以便在单个端口上侦听。注意,这与NPM提供的旧的LearnBoost"群集"模块不同。
1 2 3 4 5 6 7 8 | if (cluster.isMaster) { // Fork workers. for (var i = 0; i < numCPUs; i++) { cluster.fork(); } } else { http.Server(function(req, res) { ... }).listen(8000); } |
工人们将竞相接受新的连接,而负载最小的进程最有可能获胜。它工作得很好,可以在多核设备上很好地扩展吞吐量。
如果您有足够的负载来关心多个核心,那么您还需要做更多的事情:
在网络代理(如nginx或apache)后面运行node.js服务,它可以进行连接限制(除非您希望过载条件使框完全关闭)、重写URL、提供静态内容以及代理其他子服务。
定期回收工作进程。对于长时间运行的进程,即使是很小的内存泄漏最终也会累积起来。
设置日志收集/监视
附言:亚伦和克里斯托弗在另一篇文章的评论中有一个讨论(在这篇文章中,它是第一篇)。对此有几点看法:
- 共享套接字模型非常方便允许多个进程在单个端口上侦听并竞争接受新的连接。从概念上讲,您可以考虑使用预处理的Apache执行此操作,但要注意的是,每个进程只接受一个连接,然后死亡。Apache的效率损失在分叉新进程的开销中,与套接字操作无关。
- 对于node.js,让n个工人在单个套接字上竞争是一个非常合理的解决方案。另一种选择是建立一个与nginx类似的机顶盒前端,并将该代理流量分配给各个工作人员,在工作人员之间交替分配新的连接。这两种解决方案具有非常相似的性能特征。而且,正如我上面提到的,您很可能希望让nginx(或另一种选择)以任何方式作为您的节点服务的前端,这里的选择实际上是:
共享端口:
VS
单个端口:
可以说,单个端口设置有一些好处(可能在进程之间耦合较少、具有更复杂的负载平衡决策等),但它确实需要更多的工作来设置,而且内置集群模块是一种低复杂性的替代方案,适用于大多数人。
一种方法是在服务器上运行多个node.js实例,然后在它们前面放置一个负载均衡器(最好是非阻塞的,如nginx)。
RyanDahl在去年夏天在谷歌的技术讲座中回答了这个问题。换句话来说,"只需运行多个节点进程,并使用一些合理的方法让它们进行通信。例如sendmsg()-样式IPC或传统RPC"。
如果您想马上把手弄脏,请查看Spark2Forever模块。它使得生成多个节点进程变得非常简单。它处理设置端口共享,以便每个端口都可以接受到同一端口的连接,如果您想确保进程在死时重新启动,也可以自动重新启动。
更新-10/11/11:在节点社区中,一致认为集群现在是管理每台机器多个节点实例的首选模块。永远也值得一看。
您可以使用群集模块。看看这个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; if (cluster.isMaster) { // Fork workers. for (var i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); }); } else { // Workers can share any TCP connection // In this case its a HTTP server http.createServer(function(req, res) { res.writeHead(200); res.end("hello world "); }).listen(8000); } |
多节点利用您可能拥有的所有核心。请访问http://github.com/krizyp/multi-node。
为了满足更简单的需求,您可以在不同的端口号上启动多个节点副本,并将负载平衡器放在它们前面。
如上所述,集群将在所有核心上扩展和负载平衡您的应用程序。
添加类似
1 2 3 | cluster.on('exit', function () { cluster.fork(); }); |
将重新启动任何失败的工作人员。
现在,很多人也喜欢PM2,它为您处理集群,并提供一些很酷的监控功能。
然后,在运行集群的多台机器前面添加nginx或haproxy,这样您就有了多个级别的故障转移和更高的负载容量。
节点JS支持集群以充分利用您的CPU。如果您没有使用集群运行它,那么可能是在浪费硬件功能。
node.js中的集群允许您创建可以共享同一服务器端口的单独进程。例如,如果我们在端口3000上运行一个HTTP服务器,那么它就是在处理器的单核上运行的单线程上的一个服务器。
下面显示的代码允许您集群应用程序。此代码是node.js表示的官方代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var cluster = require('cluster'); var numCPUs = require('os').cpus().length; if (cluster.isMaster) { // Fork workers. for (var i = 0; i < numCPUs; i++) { cluster.fork(); } Object.keys(cluster.workers).forEach(function(id) { console.log("I am running with ID :" + cluster.workers[id].process.pid); }); cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); }); } else { //Do further processing. } |
查看本文了解完整教程
spark2基于现在不再保持的spark。集群是它的继承者,它有一些很酷的特性,比如每个CPU核心生成一个工作进程,并重新部署死掉的工作进程。
节点的未来版本将允许您分叉一个进程并将消息传递给它,Ryan已经声明他希望找到一些方法来共享文件处理程序,因此它不会是一个直接的Web工作者实现。
目前还没有一个简单的解决方案,但它仍然很早,而且node是我见过的最快的开源项目之一,所以希望在不久的将来有一些很棒的东西。
这里的新来的孩子是LearnBoost的"向上"。
它提供"零停机重新加载",并额外创建多个工人(默认情况下为CPU数量,但它是可配置的),以提供世界上最好的。
它是新的,但似乎相当稳定,我在当前的项目中很高兴地使用它。
我正在使用节点工作者以一种简单的方式从我的主进程运行进程。当我们等待官方的方式出现时,似乎工作得很好。
集群模块允许您使用机器的所有核心。事实上,您只需使用两个命令就可以利用这一点,并且不需要使用非常流行的流程管理器PM2来接触您的代码。
1 2 | npm i -g pm2 pm2 start app.js -i max |
您可以在多个核心上运行node.js应用程序,方法是将cluster模块与OS模块结合使用,后者可用于检测您拥有多少CPU。
例如,假设您有一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Dependencies. const server = require('./lib/server'); // This is our custom server module. const cluster = require('cluster'); const os = require('os'); // If we're on the master thread start the forks. if (cluster.isMaster) { // Fork the process. for (let i = 0; i < os.cpus().length; i++) { cluster.fork(); } } else { // If we're not on the master thread start the server. server.init(); } |
在运行一个nodejs进程的多个框之前,可以使用纯TCP负载均衡器(haproxy)将nodejs扩展到多个框中。
如果您有一些共同的知识可以在所有实例之间共享,那么您可以使用一个中央redis存储或类似的存储,然后可以从所有流程实例(例如,从所有框中)访问这些存储。
还可以将Web服务设计为多个独立的服务器来监听Unix套接字,这样您就可以将数据处理等功能推送到单独的进程中。
这类似于大多数scrpting/database web服务器体系结构,其中一个CGI进程处理业务逻辑,然后通过unix套接字将数据推送到数据库。
区别在于,数据处理是作为监听端口的节点Web服务器写入的。
它更为复杂,但最终它需要多核开发。为每个Web请求使用多个组件的多进程体系结构。