用了一段时间的Egg.js框架。文档内有主进程,agent进程和worker进程的概念。后来在看《深入浅出Node.js》时发现了这部分内容。
在看完文档之后单纯地知道一些概念,主进程会将任务分发给worker进程;worker进程抢占式地执行任务;fork进程。
进程与线程
在浏览器环境下,JS线程属于Chrome浏览器渲染进程内部。JS的执行与界面的渲染是互斥的。因此,创建了worker线程来执行较为繁重的JS任务,以此来减轻JS主线程的页面渲染阻塞。
JS主线程与worker线程的消息传递也十分简单,就是通过JS主线程onmessage方法监听消息,worker线程使用postMessage方法发送消息。
类似地,NodeJS中也提供方便的接口以进行线程间的通信
主进程与子进程监听message事件来监听消息,使用send方法来发送消息。
虽然JS的单线程的,但是为了最大限度地利用CPU资源,所以要使用多线程架构。注意,这里 并不是为了解决并发问题,而是单纯地充分使用CPU资源
IPC(Inter-Process Communication)
进程间通讯的原理就是使不同进程能够互相访问资源并进行协调工作。
在Node中使用的是libuv来实现IPC。
libuv在win系统下使用named pip 在*nix系统使用Unix Domain Socket实现。
在使用上都是通过message事件和send方法。
父进程在创建子进程之前,会先建立IPC通道,然后监听它,最后创建真正的子进程,并且通过环境变量(NODE_CHANNEL_FD)来告诉 子进程这个IPC通道的文件描述符。子进程根据文件描述符来连接已存在的IPC通道。
Node的四种创建进程方式
其中只有fork方法创建的子进程才会根据环境变量去连接IPC通道以实现进程间通讯
(其他方法创建的除非也按照约定去连接这个已经创建好的IPC通道)
node中父子进程的通讯过程
send方法包含两个参数,message与sendHandler(句柄)。
父进程发送到管道内的是字符串,子进程要格式化字符串。
子进程接受到句柄后的操作:
function(message, handle, emit){
var self = this
var server = new net.Server()
server.listen(handle, function(){
emit(erver)
})
}端口的共同监听
文章的最高是提及的概念:Egg.js的架构是主进程会将任务分发给worker进程;worker进程抢占式地执行任务。
在服务端一个进程只能监听一个端口,否则就会报错。
代理方法
最容易想到的是做一层代理,让主进程把请求分发到各个子进程中。但是这么做会使用多余的文件描述符,而系统的文件描述符是有限的,这么做会浪费资源。即,客户端与主进程建立连接会使用一个文件描述符,主进程与子进程建立连接又会使用一个文件描述符。
句柄发送与还原
从上述的父子进程通信方式可以看出,父进程(主进程)在接收到客户端的请求之后,可以把句柄(Socket TCP套接字/Server TCP服务器)发送给子进程,子进程再监听这个句柄。这也就节省掉一个文件描述符。
共同监听一个端口
独立启动进程中,TCP服务端Socket套接字的文件描述符并不相同,导致监听到相同端口报错。
Node底层对每个端口监听都设置了SO_REUSEADDR选项,不同进程可以对相同的网卡和端口进行监听,,这个服务器的套接字可以被不同的进程复用。
也就是说,只要每个子进程都能拿到相同的文件描述符,那么就可以监听相同的端口了。但是每个文件描述符同一时间只可以被每个进程所用,那么就是说只有一个子进程可以拿到这个sendHandle。这就是所谓的抢占式。
Cluster模块
node提供了cluster模块以解决多核CPU的利用率问题。也可以方便地创建Node进程集群。
本质上Cluster就是child_process与net模块的组合应用。
当然,直接使用child_process可以使用多个APP监听相同端口。更为灵活。
扩展
代码层面的分析