父子进程通过send()和on('message',callback)分别进行消息的发送和接收处理实现通信机制
自动重启
- 在主进程中通过监听子进程的exit事件来或者获知其推出的信息,在多进程架构中加入一些子进程管理的机制,比如重新启动一个进程来继续服务。
- 在极端情况下,所有的工作进程都停止接收新的连接,全处在等待退出的状态。但是等到进程完全退出才重启的过程中,所有的新来请求可能存在没有工作进程为新用户服务的情景,这会丢掉大部分请求。 此时需要为退出的流程中新增一个自杀信号(suicide).工作进程在得知要退出时,向主进程发送一个自杀信号,然后才停止接收新的连接,当所有连接断开后才退出。主进程在接收到自杀信号后,立即创建新的工作进程服务。
- 对于http服务的长连接,为已有的连接的断开设置一个超时时间是必要的,在限定时间里里强制退出的设置
- 对未能捕获的异常进行日志记录
- 为了消除无意义的重启,在满足一定规则的限制下,不应当反复重启。比如在单位时间内规定只能重启多少次,超过限制就出发giveup事件,告知放弃重启工作进程这个重要事件。为了完成限量重启的统计,使用队列做标记,并每次重启工作进程之间进行打点并判断重启是否太过频繁。
giveup事件比uncaughtException更严重的异常,因为giveup事件表示集群中没有任何进程服务了,十分危险,此时应该添加重要日志,并让监控系统监视到这个严重错误,进而报警。
demo:
master.js(主进程,控制和管理子进程)
let fork = require('child_process').fork
let cpus = require('os').cpus()
let server = require('net').createServer()
server.listen(1337)
//限制重启次数
let limit = 10
//限制时间单位
let during = 60000
let restart = []
let isTooFrequently = function () {
//记录重启时间
let time = Date.now()
let length = restart.push(time)
if (length > limit) {
// 取出最后10个记录
restart = restart.slice(limit * -1)
}
// 最后一次重启到前10次重启之间的时间间隔
return restart.length >= limit && restart[restart.length - 1] - restart[0] < during
}
let workers = {}
let createWorker = function () {
// 检查是否重启的过于频繁
if (isTooFrequently()) {
//触发giveup事件,不再重启
process.emit('giveup', restart.length, during)
return
}
let worker = fork(__dirname + '/worker.js')
// 处理自杀信号
worker.on('message', function(message) {
if (message.act === 'suicide') {
createWorker()
}
})
//退出时重新启动新的进程
worker.on('exit', function () {
console.log('Worker ' +worker.pid + ' exited.')
delete workers[worker.pid]
createWorker()
})
worker.send('server', server)
workers[worker.pid] = worker
console.log('Create worker.pid ' + worker.pid)
}
for(var i =0;i<cpus.length; i++) {
createWorker()
}
process.on('exit', function() {
for(let pid in workers) {
workers[pid].kill()
}
})
worker.js(各个工作进程)
var http = require('http')
let logger = require('./logger')
let server = http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type':'text/plain'})
res.end('handled by child,pid is ' + 'process.pid' + '\n')
throw new Error('throw exception ' + JSON.stringify(req))
})
let worker;
process.on('message', function(m, tcp) {
if(m === 'server') {
worker = tcp
worker.on('connection', function(socket) {
server.emit('connection',socket)
})
}
})
process.on('uncaughtException', function (err) {
logger.error(err)
process.send({act: 'suicide'}) //新增自杀信号
// 停止接受新的连接
worker.close(function () {
// 所有已有连接断开后,推出进程
process.exit(1)
})
// 处理长连接的退出进程机制
setTimeout(function() {
process.exit(1)
}, 5000)
})
负载均衡
- NODE默认采取操作系统的抢占式策略(即综合考虑cpu、I/O繁忙度,决定是否进行处理服务)对于不同业务,可能只需要考虑cpu的繁忙度即可
- NODE v0.11提供Round-Robin 轮叫调度,其工作方式是由主进程接受连接,将其依次分发给工作进程。分发的策略是在N个工作进程中,每次选择第i = (i+1)mod n 个进程来发送连接。
在cluster 模块中启用它的方式如下:
cluster.schedulingPolicy = cluster.SCHED_RR (启用Round-Robin)
cluster.schedulingPolicy = cluster.SCHED_NONE(不启用)
或者在环境 变量里设置NODE_CLUSTER_SCHED_POLICY
export NODE_CLUSTER_SCHED_POLICY = rr
export NODE_CLUSTER_SCHED_POLICY = none
状态共享
node不容许在多个进程之间共享数据,但是一些数据 譬如配置数据需要在多个进程之间是一致的。
1. 采用第三方数据存储
eg:redis,然后去轮讯存储的数据
2. 主动通知
创建一个通知进程,去轮询config配置,然后发送通知和查询状态去通知其他进程。推送机制可以按进程间信号传递,在跨多台服务器时会无效,故可以采用Tcp或者UDP的方案
Cluster模块
cluster模块就是child_process 和net模块的组合应用。但是cluster模块应用中,一个主进程只能管理一组工作进程。
暴露事件:
- fork:复制一个工作进程后触发该事件
- online: 复制好一个工作进程后,工作进程主动发送一条online消息给主进程,主进程收到消息后,触发该事件
- listening: 工作进程中调用listen() (共享了服务器Socket)后,发送一条listening消息到主进程,主进程收到消息后,触发该事件
- disconnect: 主进程和工作进程之间IPC通道断开后会触发该事件
- exit: 有工作进程退出时触发该事件
- setup: cluster.setupMaster() 执行后触发该事件
此时的master.js 只需要写如下代码,此时,需要在worker.js中删除throw new error
let cluster = require('cluster')
cluster.setupMaster({
exec: "worker.js"
})
let cpus = require('os').cpus()
for(let i =0;i<cpus.length;i++) {
cluster.fork()
}