使用node服务器cluster集群来实现热更新和平滑无损重启

952 阅读2分钟

使用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();

github npm 使用方式:

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
};