node学习笔记 如何充分利用多核CPU

node的单线程

node是在v8引擎上构建的,其保持了JavaScript在浏览器中单线程的特点,其最大的好处是不用在意状态的同步的问题,不会出现死锁,也没有线程上下文交换所带来的性能上的开销。但是其弱点也很明显:

  • 无法利用多核CPU
  • 错误会引起整个应用退出,应用的健壮性值得考研
  • 大量计算占用CPU导致无法继续调用异步I/O

当JavaScript长时间执行会导致长时间的CPU占用,继而会导致后续的异步I/O发不出调用,已完成的异步I/O的回调函数也会得不到及时执行。

node中的JavaScript部分执行在单线程上,而如今CPU基本为多核,真正的服务器往往还有多个CPU,一个node进程只能利用一个核,如何充分利用多核CPU服务器是亟待解决的问题。
node是基于事件驱动的方式实现,所有处理都在单线程上进行,影响事件驱动服务模型性能的点在于CPU计算能力,它的上线决定这类服务模型的性能上限,但却不受多进程或多线程模式上的资源上限的影响,可伸缩性远比前两者高,当解决掉多核CPU的利用问题,带来性能上提升是客观的。

node中的多进程架构

对此,node采用了与Web Workers相同的思路来解决单线程中大计算量的问题:child_process
子进程的出现,意味着node可以从容地应对单线程在健壮性和无法利用多核CPU方面的问题。通过将计算分发到各个子进程,有将大量计算分解掉,然后再通过进程之间的事件消息来传递结果,这可以很好地保持应用模型的单调和低依赖。通过Master-Worker的管理方式,也可以很好地管理各个工作进程,以达到更高的健壮性。

1
2
3
4
5
6
7
8
9
10
11
12
13
// work.js
var http=require('http')
http.createServer((req,res)=>{
res.writeHead(200,{'Content-Type':'text/plain'});
res.end('hello word/n');
}).listen(Math.round((1+Math.random())*1000),'127.0.0.1'); //监听1000到2000间一个随机端口
// master.js
var fork=require('child_process').fork;
var cpus=require('os').cpus();
for(var i=0;i<cpus.length;i++){
fork('./worker.js') // 根据当前机器上的CPU数量赋值相应的node进程
}

Master-Worker

Master-Worker模式,又称为主从模式。这种典型并行处理业务模式的分布式架构具备较好的可伸缩性(可伸缩性实际上是和并行算法以及并行计算机体系结构放在一起讨论的。某个算法在某个机器上的可扩放性反映该算法是否能有效利用不断增加的CPU。)和稳定性。主进程不负责具体的业务处理,而是负责调度和管理工作进程,工作进程负责具体的业务处理,所以,工作进程的稳定性是开发人员需要关注的。

启动多个进程为了更好利用多核CPU,并非为了解决并发

node提供了child_process模块,并且也提供了child_process.fork()函数实现进程的复制。通过fork()复制的进程都是一个独立的进程,这个进程中有着独立而全新的V8实例。它需要至少30毫秒的启动时间和至少10MB的内存。也就是说尽管node提供了fork()供我们赋值进程使每个CPU内核都是用上,但依然要切记fork()进程是昂贵的。
但我们需要明确的一点的是,我们在node中启动多个进程只是为了充分将CPU资源利用起来,而不是解决并发问题,因为node通过事件驱动的方式在单线程上就已经解决了并发的问题。

未完待续

nodejs一个进程监听的两个端口是串行的

参考链接

《深入浅出node.js》 朴灵著