使用node服务器cluster集群来实现热更新和平滑无损重启
项目中,要进行快速迭代,免不了经常更新node server,而每次更新需要node重启会丢掉一些连接。
可以使用cluster集群来处理这个问题,附带实现node开发中的热更新。
另外我将这个小的实现集成为了一个npm包,可以方便安装和使用。
主要逻辑
#!/usr/bin/env node
const fork = require('child_process').fork;
const __CurPath = process.cwd();
const cluster = require('cluster');
const fs = require('fs');
// 读取config文件
let configPath = __CurPath + '/grace-config.js';
const config = require(configPath);
const entryPath = __CurPath + '/' + config.entry;
const {workerNum = 2, graceReloadOnFileChange = true} = config;
const delay = async time => new Promise(resolve => setTimeout(resolve, time));
const arg2 = process.argv[2];
if (arg2 === '-v') {
const version = require(__dirname + '/package.json');
console.log(`
name :${version.name}
version :${version.version}
`);
process.exit(0);
}
// 监听task列表,由此可以触发子进程热更,每一个task对应一个子进程热更函数
const taskMap = {};
let taskLock = false;
const reStart = () => {
let worker = cluster.fork();
// 将子进程热更函数添加到taskMap,等待触发
taskMap[worker.id] = () => new Promise(resolve => {
// 执行task热更,此时删除当前task
delete taskMap[worker.id];
// 向子进程发送平滑重启信号
worker.send('reStart');
// 子程序处理完平滑重启,断开连接时
worker.on('disconnect', () => {
// 此时,子进程已经平滑断开,进行重启子进程
reStart();
// 子进程热更函数resolve
resolve(true);
});
});
return worker;
};
const graceReload = async () => {
if (taskLock) {
return;
}
taskLock = true;
console.log('start graceReload');
// 处理task
for (let key in taskMap) {
if (taskMap.hasOwnProperty(key)) {
// 处理taskMap
await taskMap[key]();
await delay(500);
}
}
console.log('success graceReload');
taskLock = false;
};
const main = async () => {
if (cluster.isMaster) {
// 父进程
for (let i = 0; i < workerNum; ++i) {
reStart();
await delay(1000);
}
fs.watch(__CurPath, (event, filename) => {
if (!graceReloadOnFileChange) {
return;
}
console.log(`文件变动:${filename}`);
graceReload();
});
}
else {
// 启动入口server
let server = require(entryPath);
// 子进程
cluster.worker.on('message', async msg => {
if (msg === 'reStart') {
// 此处平滑关闭
server.close();
// 断开连接
cluster.worker.disconnect();
setTimeout(() => {
// 5s不断开,强制退出
process.exit(1);
}, 5000);
}
});
}
};
main();
npm i -g grace-server
grace index.js
server代码:
const http = require('http');
let server = http.Server((req, res) => {
res.writeHead(200, {
'content-type': 'text/html; charset=utf-8'
});
res.end('hello');
});
let port = 8000;
server.listen(port, () => {
console.log('listen on' + port);
});
// 注意这里需要导出server,以供给grace进行平滑关闭。
module.exports = server;
grace.config.js
module.exports = {
entry: './index.js',
workerNum: 2,
graceReloadOnFileChange: true
};