前文介绍了NodeJs子进程创建和进程间通信,但让开发者处理多进程的管理是比较麻烦的事情,通常开发者只希望关注业务代码的实现。本文介绍了多进程管理相关的cluster模块、工具、负载均衡等。
相关文章
- 学习NodeJs多进程(一)
- 学习NodeJs多进程(二)
目录
- cluster模块
- 负载均衡
- 进程管理工具
- cluster不适用的场景
cluster模块
cluster模块是对多进程创建、消息传递、共享端口的封装。以下为官方的使用示例,根据cpu数量创建工作进程,并监听同一个端口8000
。
const cluster = require('cluster')
const http = require('http')
const numCPUs = require('os').cpus().length
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`)
// Fork workers.
for (let i = 0; i < numCPUs; i++) {
cluster.fork()
}
cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`)
})
} else {
// Workers can share any TCP connection
// In this case it is an HTTP server
http.createServer((req, res) => {
res.writeHead(200)
res.end('hello world\n')
}).listen(8000)
console.log(`Worker ${process.pid} started`)
}
在前文介绍的多进程监听同一端口中,主进程创建服务器,并将服务器句柄传递给子进程,子进程再根据句柄还原服务器实例,请求到来时由操作系统调度机制决定交给哪个进程处理。以上由于保留了多个服务器实例直接监听同一端口,会造成资源浪费,并且请求转发不可控。
cluster模块中默认以轮询的方式处理请求,父进程创建内部服务器并监听端口,所有请求先经过该服务器,再转发给子进程处理。由于父进程负责转发,因此父进程可以指定负载均衡策略,另外父进程不直接处理业务逻辑,可以保证父进程的稳定性。
cluster模块提供丰富的api用来管理子进程,以下示例为子进程挂掉后自动创建新的进程:
const cluster = require('cluster')
const http = require('http')
cluster.schedulingPolicy = cluster.SCHED_RR
function listen (worker) {
worker.on('message', (msg) => {
console.log(`worker ${worker.process.pid}:`, msg)
})
}
if (cluster.isMaster) {
console.log(`master process ${process.pid} is running`)
for (let i = 0; i < 4; i++) {
cluster.fork()
}
for (const id in cluster.workers) {
listen(cluster.workers[id])
}
cluster.on('exit', (worker, code, signal) => {
console.log(`worker process ${worker.process.pid} is exited`)
listen(cluster.fork())
})
} else {
http.createServer((req, res) => {
res.writeHead(200)
res.end('hello world')
process.send('handled request')
}).listen(8000)
console.log(`worker process ${process.pid} is running`)
}
访问localhost:8000,再手动kill掉某一进程,执行结果如下:
master process 8616 is running
worker process 16332 is running
worker process 4624 is running
worker process 8084 is running
worker process 16352 is running
worker 16332: handled request
worker 15172: handled request
worker process 15172 is exited
worker process 2260 is running
worker 16332: handled request
worker 8084: handled request
worker 2260: handled request
可以看到,只要有子进程退出,会自动创建子进程。只要有工作进程存在,就能继续处理请求。
负载均衡
cluster模块提供两种负载均衡策略
-
cluster.SCHED_RR
轮询分发(除Windows外所有平台的默认方法),由主进程负责监听端口,接收新连接后再将连接循环分发给工作进程。在分发中使用了一些内置技巧防止工作进程任务过载。
-
cluster.SCHED_NONE
使用操作系统调度机制,主进程创建监听socket后发送给感兴趣的工作进程,由工作进程负责直接接收连接。由于操作系统调度机制的难以捉摸,可能会使分发变得不稳定。在cluster模块出现之前,多进程共同监听同一端口的调度方式都是用该机制。
使用cluster.schedulingPolicy
可指定以上两种策略,也可以使用NODE_CLUSTER_SCHED_POLICY
环境变量,对应以上的两种策略的取值为rr
、none
。
cluster不支持自定义负载均衡策略,可参考cluster的源码,实现自己的负载均衡策略。根据cluster模块源码学习来理解多进程的负载均衡算法,文章地址:http://www.cnblogs.com/accordion/p/7207740.html
进程管理工具
在生产环境中,除了使用多进程充分利用cpu性能外,还需考虑进程异常退出的自动重启,多个工作进程共享资源,以及多进程之间的调度。cluster模块只是对多进程创建、消息传递、共享端口的简单封装,下面介绍一些目前成熟的进程管理工具。
-
Pandora.js 是一个 Node.js 应用监控管理器,它集成了多种类型的能力诸如:监控、链路追踪、调试、进程管理等等。它提供多进程模型、多进程管理以及自定义进程扩展能力。开发者只需关注具体的业务逻辑实现即可。
-
是一个进程管理工具,维护一个进程列表,可以用它来管理你的node进程,负责所有正在运行的进程,并查看node进程的状态,也支持性能监控,自动重启,负载均衡等功能,而且使用非常简单。
-
Egg
虽然是企业级的NodeJs开发框架,但集成了egg-cluster
模块实现多进程的管理,自带服务管理的cli,不需要其他的进程管理工具来管理进程了。另外提供Agent进程来处理不适合用多进程处理的场景,如定时任务、websocket等。
cluster不适用的场景
这里的cluster不止指node的cluster模块,也包括通过第三方库实现的多进程管理工具。cluster虽然能充分利用服务器cpu资源,提高服务的健壮性,但并不是所有场景都适合用多进程来处理,比如:
-
定时任务
定时任务应该只被一个进程调用,如果以多进程的模式启动,会造成定时任务的重复执行。通常应该单独创建一个进程来处理定时任务,并且该进程应该保证稳定性,在发生异常错误时记录错误而不自动退出。
-
socket.io
socket.io是在websocket上封装的,在客户端未提供websocket功能的基础上使用xhr polling、jsonp或forever iframe的方式进行兼容,同时在建立ws连接前往往通过几次http轮询确保websocket服务可用。在建立连接前的几次http轮询如果被转发到了不同的进程,会导致ws服务连接失败。socket.io官方提供了
nginx反向代理 + iphash
的方式来保证同一个客户端的多次请求定位到后端同一个服务进程。关于socket.io的集群策略,可参考:https://segmentfault.com/a/1190000009622158- session和内存缓存
使用cluster模块通常是无状态应用,因为在进程中缓存的数据无法直接与其他进程共享,理论上可以在主进程上维护统一的缓存数据,然后子进程从父进程中获取,但逻辑上比较复杂,并且增加了父进程的负担。因此常常使用第三方工具来共享应用状态,如
redis
。
总结
本文介绍了cluster模块和常用的多进程管理工具,希望对想要使用多进程来提升服务的性能和健壮性的小伙伴有一些帮助。
本文参考资源如下: