转载

解读node的cluster模块

在如今机器的CPU都是多核的背景下,Node的单线程设计已经没法更充分的"压榨"机器性能了。所以从v0.8开始,Node新增了一个内置模块——“cluster”,故名思议,它可以通过一个父进程管理一坨子进程的方式来实现集群的功能。

快速上手

使用十分的简单,如下

var cluster = require ( 'cluster' ) ;

var http = require ( 'http' ) ;

var numCPUs = require ( 'os' ) . cpus ( ) . length ; // 获取CPU的个数

if ( cluster . isMaster ) {

for ( var i = 0 ; i & lt ; numCPUs ; i ++ ) {

cluster . fork ( ) ;

}

cluster . on ( 'exit' , function ( worker , code , signal ) {

console . log ( 'worker ' + worker . process . pid + ' died' ) ;

} ) ;

} else {

http . createServer ( function ( req , res ) {

res . writeHead ( 200 ) ;

res . end ( "hello world/n" ) ;

} ) . listen ( 8000 ) ;

}

稍微解释下,通过isMaster属性,判断是否Master进程,是则fork子进程,否则启动一个server。每个HTTP server都能监听到同一个端口。

但是在实际项目中,我们的启动代码一般都已经封装在了app.js中,要把整块启动逻辑嵌在上面的if else中实在不优雅。 所以,我们可以这样:

var cluster = require ( 'cluster' ) ;

var numCPUs = require ( 'os' ) . cpus ( ) . length ;

if ( cluster . isMaster ) {

for ( var i = 0 ; i & lt ; numCPUs ; i ++ ) {

cluster . fork ( ) ;

}

// 其它代码

} else {

require ( "./app.js" ) ;

}

简单之处就在于原本的应用逻辑根本不需要知道自己是在集群还是单边。(当然,如果应用在内存中维护了某些状态,比如session,就需要运用某些机制来共享了,这里不详说)

常用API

cluster模块提供了一大坨事件和方法,这里挑一些常用的说明下,详细的请参考官方文档。

cluster.setupMaster([settings])

setupMaster用来改变默认设置,只能被调用一次,调用后,配置会存在且冻结在cluster.settings里。配置只会影响fork时的行为,实际上这些选项就是传给fork用的,有兴趣的同学可以去对照child_process.fork()的参数。

具体有如下选项:

  • execArgv Node执行时的变量数组,传递给node(默认为process.execArgv)。
  • exec 执行的文件,配置后就不需要像最开始的例子,在代码里require目标文件了(默认为process.argv[1])。
  • args 传递给worker的变量数组(默认为process.argv.slice(2)))。
  • silent 是否禁止打印内容(默认为false)。
  • uid 设置进程的用户ID。
  • gid 设置进程的组ID。

Event: fork和online

当一个新的worker被fork时就会触发fork事件,而在worker启动时才会触发online事件,所以fork先触发,online后触发。

可以在这两个事件的callback里做些初始化的逻辑,也可以在这时向master报告:“我起来了!”。

Event: exit

当任何一个worker停掉都会触发exit事件,可以在回调里增加fork动作重启。

通过worker.suicide来判断,worker是意外中断还是主动停止的(在worker中调用kill和disconnect方法,视作suide。)。

cluster . on ( 'exit' , function ( worker , code , signal ) {

console . log ( 'worker %d died (%s). restarting...' ,

worker . process . pid , signal || code ) ;

cluster . fork ( ) ;

} ) ;

cluster.worker和cluster.workers

前者是一份worker对象的引用,只能在worker里使用。

后者是master下对当前可用worker的一个Object,key为worker id,注意,当worker已经exit或disconnect后就不会在这个object里了。

Event: message

message事件可以用来做master和worker的通信机制。 这里是个 例子 。

利用这套机制,可以用来实现不间断重启, 代码 。

文章最开始的例子有个问题,尤其是运行在生产环境还不够健壮:如果某个worker因为意外“宕机”了,代码并没有任何处理,这时如果我们重启应用又会造成服务中断。利用这些API就可以利用事件监听的方式做相应处理。

每个worker进程通过使用child_process.fork()函数,基于IPC(Inter-Process Communication,进程间通信),实现与master进程间通信。

什么是fork,Linux API给了如下解释

fork() creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is referred to as the parent process.

The child process and the parent process run in separate memory spaces. At the time of fork() both memory spaces have the same content. Memory writes, file mappings (mmap(2)), and unmappings (munmap(2)) performed by one of the processes do not affect the other.

我们可以看到,fork出的子进程拥有和父进程一致的数据空间、堆、栈等资源(fork当时),但是是独立的,也就是说二者不能共享这些存储空间。 那我们直接用fork自己实现不就行了,干嘛需要cluster呢。

“这样的方式仅仅实现了多进程。多进程运行还涉及父子进程通信,子进程管理,以及负载均衡等问题,这些特性cluster帮你实现了。”

这里再说下cluster的负载均衡。Node.js v0.11.2+的cluster模块使用了 round-robin 调度算法做负载均衡,新连接由主进程接受,然后由它选择一个可用的worker把连接交出去,说白了就是轮转法。算法很简单,但据官方说法,实测很高效。

注意:在windows平台,默认使用的是 IOCP ,官方文档说一旦解决了分发handle对象的性能问题,就会改为RR算法(没有时间表。。)

如果想用操作系统指定的算法,可以在fork新worker之前或者setupMaster()之前指定如下代码:

cluster . schedulingPolicy = cluster . SCHED_NONE ;

或者通过环境变量的方式改变

$ export NODE_CLUSTER_SCHED_POLICY = "none" # "rr" is round - robin

$ node app . js

或在启动Node时指定

$ env NODE_CLUSTER_SCHED_POLICY = "none" node app . js

使用pm2实现cluster

pm2 是一个现网进程管理的工具,可以做到不间断重启、负载均衡、集群管理等,比forever更强大。利用pm2可以做到 no code but just config 实现应用的cluster。

安装pm2什么的这里就不赘述了。用pm2启动时,通过-i指定worker的数量即可。如果worker挂了,pm2会自动立刻重启,各种简单省心。

$ pm2 start app . js - i 4

解读node的cluster模块

也可以在应用运行时,改变worker的数量,如下图

解读node的cluster模块

更多的使用方法,可以去github上慢慢看(说句题外话,如果有类似PM2,甚至更好的PM工具,欢迎在评论里回复^_^)。

多机器集群

cluster适用于在单台机器上,如果应用的流量巨大,多机器是必然的。这时,反向代理就派上用场了,我们可以用node来写反向代理的服务(比如用 http-proxy ),好处是可以保持工程师技术栈的统一,不过生产环境,我们用的更多的还是nginx,这里就不多介绍了。

参考文章

正文到此结束
Loading...