`
haiyupeter
  • 浏览: 427408 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

NodeJS Server 进程自动重启

阅读更多

背景:

NodeJS的特点是单进程,事件驱动,非阻塞式IO编程,当主进程抛异常挂掉,整个NodeJS Server就会停止。

 

对当前的NodeJS进程进行监控非常重要,NodeJS的进程的停止时,能在指定时间内重启动,继续提供服务。

 

 

思路:

1.起一个守护进程,用于与各子进程(虚拟进程)进行心跳通信,官运亨通护进程监测子进程是否有回应,若三次连接后没有回应,则将该进程进行重启。

2.子进程与守护进程进行心跳通信,若守护进程不存在,则子进程自动退出。

 

示例图:

 


守护进程:bootstrap.js

/**
 * @author wubocao
 * node程序启动入口模块
 * 1:设置允许环境当前路径cwd为该文件目录
 * 2:启动守护进程,运行主服务
 * 3:监听关闭事件,关闭主服务并退出
 */
//日志
console.log("start bootstrap");

var path = require("path");
var addDeamon = require("./deamon.js").addDeamon;

var file = require.main.filename, path = path.dirname(file);
process.chdir(path);

var modulesNames = [], args = [], deamons = [];

if (process.argv && process.argv.length) {
    for ( var i = 0, len = process.argv.length; i < len; i++) {
        if (process.argv[i] == '-m') {
            var names = process.argv[++i];
            if (names) {
                modulesNames = modulesNames.concat(names.split("|"));
            }
        } else if (process.argv[i] == '-ppid') {//过滤掉ppid参数
            i++;
            continue;
        } else {
            args.push(process.argv[i]);
        }
    }
}
// 可以在此处设置默认载入默认模块
if (modulesNames.length == 0) {
    console.log('please defined the modules like: node bootstrap.js -m main1.js -m main2.js');
    return;
    // modulesNames.push('main');
}

console.log(modulesNames);

modulesNames.forEach(function(moduleName) {
    deamons.push(addDeamon(moduleName, args));
});

process.on("exit", function() {
    console.log("parent exit");
    deamons.forEach(function(deamon) {
        deamon.stop();
    });
});

process.on("SIGQUIT", function() {
    console.log("request for exit");
    deamons.forEach(function(deamon) {
        deamon.stop();
    });
    process.exit(0);
});

 

守护进程新建一个或者多个daemon对象,每一个daemon启动一个新的业务进程:daemon.js

/**
 * @author wubocao
 * 守护进程模块
 * 使用addDeamon(model,args,option)来添加一个守护进程
 * 该函数返回一个守护进程对象,通过调用该对象的stop和init来停止和重新启动该进程
 * 
 */
var cp = require("child_process");
var util = require("util");

//对象深拷贝
function copyObj(obj, stack) {
    stack = stack || [];
    var t;
    if (obj == null) {
        return t;
    }
    if (util.isArray(obj)) {// 数组
        var instance = copyObj.getStack(obj, stack);
        if (instance) {
            return instance;
        }
        var len = obj.length;
        t = new Array(len);
        stack.push([ obj, t ]);
        for ( var i = 0; i < len; i++) {
            t[i] = copyObj(obj[i]);
        }
    } else if (typeof obj == "object") {
        var instance = copyObj.getStack(obj, stack);
        if (instance) {
            return instance;
        }
        t = {};
        stack.push([ obj, t ]);
        for ( var k in obj) {
            t[k] = copyObj(obj[k]);
        }
    } else {
        t = obj;
    }
    return t;
}
copyObj.getStack = function(obj, stack) {
    for ( var i = stack.length; i--;) {
        if (stack[i][0] === obj) {
            return stack[i][1];
        }
    }
    return null;
};

// 守护进程对象
function deamon(model, args, option) {
    if (!model || typeof model != "string") {
        throw new Error("illegal model argument");
    }
    var __args;
    if (args) {
        if (util.isArray(args)) {
            __args = copyObj(args);
        } else {
            __args = [ args ];
        }
    }
    var __opt;
    if (typeof option == "object") {
        __opt = copyObj(option);
    } else {
        __opt = {};
    }
    this.__model = model;
    this.__args = __args;
    this.__opt = __opt;
    this.__cpr = null;
    this.__cprid = 0;
    this.__heartbeat = 0;
    this.init();
}
deamon.prototype = {
    init : function() {
        if (this.__cpr) {
            return;
        }
        this.__kill = false;
        console.log("deamon init");
        var exeTime = this.__opt.timeout;
        var start = new Date().getTime();
        var context = this;
        (function run() {
            console.log("process start");
            context.__cpr = cp.fork(context.__model, context.__args, context.__opt);
            context.__cprid = context.__cpr.pid;
            context.__cpr.on("exit", function(e) {
                console.log("process exit");
                if (context.__kill) {
                    return;
                }
                if (exeTime > 0) {
                    var end = new Date().getTime();
                    if (end - start < exeTime) {
                        run();
                    } else {
                        context.__cpr = null;
                        context.__cprid = 0;
                    }
                } else {
                    run();
                }
            });
            context.__cpr.on("message", function(message) {
                if (typeof message == "object") {
                    switch (message.name) {
                    case "proccessInfo":// 进程信息(心跳检查)
                        context.__messageCall && context.__messageCall(message.value);
                        break;
                    case "broadcast":// 经常广播消息
                        try {
                            context.__cpr.send(message.value);
                        } catch (e) {
                            console.error("broadcast message error:", e);
                        }
                        break;
                    }
                }
            });
        })();
        // 开始监控心跳
        this.startHeartbeat();
    },
    stop : function() {
        if (this.__cpr) {
            console.log("deamon stop");
            this.__kill = true;
            this.__cpr.disconnect();
            this.__cpr.kill('SIGQUIT');
            this.__cpr = null;
            this.__cprid = 0;
        }
    },
    stopForce : function() {
        if (this.__cpr) {
            console.log("deamon stop force");
            this.__kill = true;
            // this.__cpr.kill('SIGKILL');
            cp.exec("kill -9 " + this.__cprid);
            this.__cpr = null;
            this.__cprid = 0;
        }
    },
    getInfo : function(callback, msg) {
        if (this.__cpr) {
            this.__messageCall = callback;
            try {
                if (msg) {
                    console.log("try get child process info with message[" + msg + "]");
                }
                this.__cpr.send({
                    name : "proccessInfo",
                    msg : msg || ""
                });
            } catch (e) {
                console.error("send message 'proccessInfo' error:", e);
            }
        } else {
            console.error("no child process when get child process info");
        }
    },
    //开始心跳
    startHeartbeat : function() {
        var deamon = this;
        //先停掉原来的心跳
        this.stopHeartbeat();
        //times为监控心跳连续失败次数
        var times = 0;
        //心跳检查
        function checkDeamon() {
            //做1500毫秒等待,判断deamon子进程是否挂掉
            var t = setTimeout(function() {
                times++;
                t = 0;
                if (times >= 3) {
                    console.log("heart check with no response more then 3 times,restart now");
                    times = 0;
                    deamon.stopHeartbeat();
                    deamon.stopForce();
                    setTimeout(function() {
                        deamon.init();
                    }, 1000);
                }
            }, 1500);
            deamon.getInfo(function(memInfo) {
                if (t != 0) {
                    clearTimeout(t);
                    t = 0;
                }
                times = 0;
                //console.log(memInfo);
            }, times > 0 ? "retry with times:" + times : "");
        }
        //每5秒获取一下
        this.__heartbeat = setInterval(checkDeamon, 5000);
    },
    //停止心跳
    stopHeartbeat : function() {
        this.__heartbeat = this.__heartbeat && clearInterval(this.__heartbeat);
    }
};

exports.addDeamon = function(model, args, option) {
    args = args || [];
    // 过滤掉ppid参数
    for ( var i = 0, len = args.length; i < len; i++) {
        if (args[i] == '-ppid') {
            i++;
        }
    }
    return new deamon(model, args.concat([ '-ppid', process.pid ]), option);
}

监控进程monitor.js,此JS由业务JS引入,用于和daemon进行心跳通信,确保进程是活动进程:

require('./monitor/module_listener.js');
(function(){
    // 开始心跳,与父进程联系
    if (process.argv && process.argv.length) {
        for ( var i = 0, len = process.argv.length; i < len; i++) {
            if (process.argv[i] == '-ppid') {// ppid参数,由父进程启动的
                console.log('startHB');
                startHB();
                break;
            }
        }
    }
    // 开始心跳
    function startHB() {
        // 退出信号处理
        process.on("SIGQUIT", function() {
            console.log("request for exit");
            process.exit(0);
        });

        // 与父进程断开联系信号处理
        process.on("disconnect", function() {
            console.log("request for exit");
            process.exit(-1);
        });

        // 心跳消息处理
        process.on("message", function(message) {
            console.log('child receive msg: ' + message);
            if (typeof message == "object") {
                if (message.name == "proccessInfo") {
                    process.send({
                        name : "proccessInfo",
                        value : process.memoryUsage()
                    });
                }
            } else if (typeof message === "string") {
                switch (message) {
                case "heartbeat":// 心跳回包
                    if (heartbeatTimer) {
                        times = 0;
                        clearTimeout(heartbeatTimer);
                        heartbeatTimer = 0;
                    }
                    break;
                }
            }
        });

        // times为监控心跳连续失败次数
        var times = 0, heartbeatTimer;

        // 心跳检查
        function checkParent() {
            // 做1500毫秒等待,判断deamon子进程是否挂掉
            heartbeatTimer = setTimeout(function() {
                times++;
                t = 0;
                if (times >= 3) {
                    times = 0;
                    console.log("heart check with no response more then 3 times,exit now");
                    process.exit(-1);
                }
            }, 1500);

            times > 0 && console.log("try get parent heartbeat " + times + " times");
            //心跳发包
            process.send({
                name : "broadcast",
                value : "heartbeat"
            });
        }

        //每5秒获取下
        setInterval(checkParent, 5000);
    }
})();

 

业务示例:

main1.js:

var http = require('http');
console.log('init main1: pid = ' + process.pid);
require('./monitor.js');
http.createServer(function(req, res){
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World, main1 \n');
}).listen('8938');
console.log('main1 server running at http://127.0.0.1:8938');

main2.js

var http = require('http');
console.log('init main2: pid = ' + process.pid);
require('./monitor.js');
http.createServer(function(req, res){
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.write('afdfadfdafdas');
    res.end('Hello World main 2\n');
}).listen('8937');
console.log('main2 server running at http://127.0.0.1:8937');

 

注意:需要在main1.js和main2.js中引入

启动进程:

node bootstrap.js -m main1.js -m main2.js

 

源码中,还包含了一个node.sh,用于管理start或者是restart, stop 等操作:

sudo chmod +x node.sh

./node.sh bootstrap.js -m main1.js -m main2.js start

 

  • 大小: 13 KB
分享到:
评论
1 楼 fwyhf 2013-11-13  
require('./monitor/module_listener.js');
请问这个文件从哪里来的?

相关推荐

    windows下定时重启nodejs程序

    首先,`windows下定时重启nodejs程序`这个标题涉及到的关键技术点包括: 1. **Windows计划任务**:Windows操作系统内置了计划任务功能,可以安排程序在特定时间执行,用于定时重启Node.js服务非常实用。通过创建新...

    NodeJS学习手册.pdf

    6. **单进程、单线程**:尽管 NodeJS 默认使用单线程,但可以通过工作进程(worker process)来实现多线程处理计算密集型任务。 安装 NodeJS 在 Windows 上相对简单,只需从官方网站下载安装包,按照步骤完成安装,...

    nodejs脚本centos开机启动实操方法

    这一篇是使用pm2实现nodejs的自动重启。 什么是pm2? 如官网介绍的,pm2是nodejs下先进的,生产进程管理器。如性能监控,自动重启、负载均衡等等。 关于pm2的更多教程,请移步pm2官方文档 1.请确保安装了node 2.安装...

    nodeJS-webServer-exercise

    在开发过程中,可以使用`nodemon`工具自动重启服务器以反映代码更改。安装并配置`nodemon`后,用`nodemon server.js`命令启动服务器。 在部署到生产环境时,考虑使用PM2进程管理器确保服务器稳定运行: ```bash ...

    deploy-nodejs

    可能需要设置环境变量,配置SSL证书以实现HTTPS,以及设置自动重启机制,确保应用在崩溃时能自动恢复。 6. **部署应用**:将构建后的应用文件上传到服务器。这可以通过FTP、SCP、Docker或其他平台提供的部署工具...

    【JavaScript源代码】nodeJs项目在阿里云的简单部署.docx

    当检测到代码更改时,nodemon会自动重启服务,确保服务始终在线。 在上传项目到服务器时,应避免上传整个`node_modules`目录,因为它通常包含大量的依赖包,会延长上传时间。你可以删除`node_modules`,仅上传核心...

    koa-server.zip

    7. **PM2**: PM2是一个Node.js进程管理工具,能够实现应用的负载均衡、自动重启、日志管理和集群部署,确保服务的高可用性。 8. **小型工作服务**: 指的是这个项目适合用于中小型企业或个人开发的小型Web服务,它...

    node.js express框架

    3. 进程管理:PM2提供了诸如列出所有进程(`pm2 list`)、重启应用(`pm2 restart all`)、查看日志(`pm2 logs`)等命令,方便开发者进行实时监控和维护。 4. 配置文件:通过创建和编辑pm2的配置文件(如pm2....

    独立部署小程序基于nodejs的服务器过程详解

    3. 安装PM2,这是一个Node.js应用的进程管理器,可确保服务稳定运行: ```bash npm install pm2 -g ``` 接下来,你需要修改源代码中的appid和appSecret,这些可以在微信小程序后台的开发设置中获取。将包含...

    reactjs-nodejs-expressjs-mongodb-assignment-master.rar

    6. **开发与部署**:项目可能使用`webpack`或`create-react-app`进行打包,`nodemon`监控文件变化自动重启服务器,`pm2`用于生产环境的进程管理。部署时,需确保Node.js环境安装,然后在服务器上运行启动脚本。 这...

    NodeJS--完整指南:NodeJS-完整指南

    - **PM2**:介绍 PM2,一个生产级别的 Node.js 应用进程管理器,提供自动重启和负载均衡功能。 通过本“Node.js 完整指南”,你将全面掌握 Node.js 开发所需技能,无论你是初学者还是有经验的开发者,都能从中受益...

    node-kingo-server:青果教务系统代理网站

    6. **自动化部署与监控**:项目可能集成了如PM2这样的进程管理工具,用于应用的启动、重启和负载均衡,以及日志管理和性能监控。 7. **版本控制与代码协作**:文件名"node-kingo-server-master"暗示该项目可能使用...

    nodejs_demo

    6. **运行与部署**:项目运行时,开发者通常会在命令行中使用`node app.js`或`nodemon app.js`(如果使用nodemon来自动重启服务器)来启动服务。在生产环境中,可以使用PM2这样的进程管理器来确保服务的稳定运行。 ...

    nodejs-stuff:我要记住的Node.js库

    14. **性能监控**:`pm2`是一个进程管理工具,可以实现应用的负载均衡、自动重启和性能监控。 15. **CLI工具**:`commander`或`yargs`帮助构建命令行接口,使Node.js程序更易用。 "nodejs-stuff-master"可能包含了...

    express-mongodb:nodejs + express + mongodb

    `nodemon`是一个监控文件更改的工具,当检测到代码变更时,它可以自动重启服务,节省手动重启的时间。在命令行中输入`nodemon`,服务就会启动。如果`nodemon`不是全局安装的,需要使用`npx nodemon`来运行。 ### ...

    详解NodeJs项目 CentOs linux服务器线上部署

    使用yarn安装pm2后,可以运行pm2 start server.js来启动项目,这样pm2会保持应用运行在后台,并且如果应用崩溃,pm2还可以自动重启它。 以上就是Node.js项目在CentOS Linux服务器上进行线上部署的详细步骤。需要...

Global site tag (gtag.js) - Google Analytics