nodejs实践录:pm2实验测试记录

409 阅读3分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

本文是测试在运行时更新ecosystem配置文件的记录。

源码

服务端

/*
express简单示例
*/
//const log = require('../lib/log.js');
const http = require('http');
const express = require('express');

var cluster = require('cluster');
var os      = require('os');

// const g_port = 4000;
const g_port = process.env["SERVER_PORT"];
const g_name = process.env["NODE_ENV"];

function test1()
{
app = express();

const server = http.createServer(app);

// 监听端口
server.listen(g_port, () => {
  console.log(`111Express is listening on ${g_port}`);
});

// 访问首页,显示当时时间
app.get('/', function (req, res) {
    console.log('111 got request from ...',  req.url);
    str = 'Hello World! now is: <br>';
    myDate = new Date();
    myDate.toLocaleString();
    str += myDate;
    res.send(str);
});

process.on('SIGINT', () => {

    console.log('111 got sigint...\n');

    const cleanUp = () => {

    };

    // 如果服务器关闭,延时2秒退出
    server.close(() => {
    // Stop after 10 secs
    setTimeout(() => {
      cleanUp();
      process.exit();
    }, 2000);
    });

    // Force close server after 15 secs
    setTimeout((e) => {
    console.log('Forcing server close !!!', e);
    cleanUp();
    process.exit(1);
    }, 3000);

});
}

var g_cnt = 0;
// 这里创建worker是否必要?
// 解答:使用pm2的cluster模式,就不用手动写代码
function test2()
{
    var numCPUs = 2; //os.cpus().length;

    if (cluster.isMaster) {  
      // Master:
      // Let's fork as many workers as you have CPU cores

      for (var i = 0; i < numCPUs; ++i) {
        cluster.fork();
      }
    } else {
      // Worker:
      // Let's spawn a HTTP server
      // (Workers can share any TCP connection.
      //  In this case its a HTTP server)
      console.log(`111 httpserver is listening on ${g_port}`);

      http.createServer(function(req, res) {
        res.writeHead(200);
        
        console.log('got request from ...',  req.url);
        str = 'here is my voice: Hello World! now is: \n';
        myDate = new Date();
        myDate.toLocaleString();
        str += myDate;
        g_cnt++;
        var cnt = '  cnt: ' + g_cnt.toString() + '\n';
        str += cnt;
        
        res.end(str);
        
      }).listen(g_port);
    }

}

// 该函数不使用worker方式
function test3()
{
  console.log(`httpserver[${g_name}] is listening on ${g_port}`);

  http.createServer(function(req, res) {
    res.writeHead(200);
    
    console.log('111 got request from ...',  req.url);
    str = '111 here is my voice: Hello World! now is: \n';
    myDate = new Date();
    myDate.toLocaleString();
    str += myDate;
    g_cnt++;
    var cnt = '  cnt: ' + g_cnt.toString() + '\n';
    str += cnt;
    
    res.end(str);
    
  }).listen(g_port);

}

// main函数
function main()
{
    //test1();
    //test2();
    test3();
}

main();

客户端


/*
用于测试http服务器连接性,每0.5秒发送GET请求,如连接不上,返回错误,打印err
*/

var request = require('request');

const log = require('../lib/log.js');

const g_port = 4000;

function httpGet(url, requestData)
{
    var head = {
        url: url,
        method: "GET",
        json: true,
        headers: {
            "content-type": "application/json",
            'User-Agent': 'git-oschina-hook',
            'X-Gitee-Token': 'webhook password',
            'X-Gitee-Event': 'Push Hook'
        },
        body: requestData
        // body: JSON.stringify(requestData)
    };

    request(head, function fn(error, response, body) {
        if (!error && response.statusCode == 200) {
            log.print('=============GET content: \n', body) // 成功
        }else{
            log.print('err');
        }
    });
}

function getHttp()
{
    var url = 'http://127.0.0.1:'+ g_port;
    var msg = new Object();
    msg['devid'] = '123456798';
    msg['station'] = '01';
    msg['stateCode']=='501';
    //log.print('will get the http.');
    httpGet(url, msg);
}

var g_loopId = null;

function startLoop()
{
    g_loopId = setInterval(loopFunc, 500);
}

function stopLoop()
{
    clearInterval(g_loopId);
}

function loopFunc()
{
    getHttp();
}

function main()
{
    log.print('start looping...');
    startLoop();
}

main();

配置文件ecosystem.config.js

module.exports = {
  // APP是一个数组,可以有多个
  // 参数参考:https://pm2.io/doc/en/runtime/reference/ecosystem-file/
  apps : [
    {
    name: 'app',
    script: 'server.js',
    args: 'null',
    instances: 1,
    //exec_mode: "cluster", // 集群模式,如不指定,默认为fork
    autorestart: false,
    min_uptime: "60s",
    max_restarts: 3,
    watch: false,
    //error_file: "./logs/app-err.log",
    //out_file: "./logs/app-out.log",
    log: "./logs/app.log",
    //log_date_format: "YYYY-MM-DD HH:mm Z", // pm2 log添加日期
    max_memory_restart: '1G',
    //listen_timeout: 3000,
    kill_timeout: 3000,
    // wait_ready: true,
    env:
    {
      NODE_ENV: 'development',
      SERVER_PORT: "4000",
    },
    env_production:
    {
      NODE_ENV: 'production1',
      SERVER_PORT: "4000",
    }
  }
  ]
};

使用

启动服务端:

pm2 start

使用客户端连接,并观察输出日志:

node client.js

测试使用的命令:

重启pm2配置文件
pm2 reload ecosystem.config.js
重启实例
pm2 reload <ID>

测试项

下面分别进行不同的测试,大部分只写结论。

更新env中的字段值

可行。

如果修改的是env_production,则:

pm2 reload ecosystem.config.js --env production

另外在命令后面,加上不加上 --update-env ,好像没影响。

更新非env的字段

使用pm2 reload ,没效果,不成功。

新加实例

原来只有一个app,再加上一个app,但要修改名称和端口号,其它可相同。使用pm2 reload ,可以看到有新的实例运行。

减少实例

原来有2个app,减少一个。
使用pm2 reload ,没效果,不成功。

实例改名

原来app改名,端口没变化: 会启动新的实例,但提示端口被占用(因为没改)。 结论:只要名称不一样,pm2就认为是新的实例,要启动。打印信息会提示[PM2][WARN] Applications app1 not running, starting...之类。所以,在mp2运行过程中,有一种方式“更新”实例,先将旧的实例停止(否则会提示占用端口号),将旧实例配置参数复制一份,将实例名称改为新的名称。再reload配置文件。更好的方法请参考下面示例。

删除实例再启动

pm2 del 1 (实例ID为1)
pm2 reload ecosystem.config.js

此时服务端的实例ID为2,1已经不存在了,但服务名称是一致的。

删除实例,修改实例,再启动

同上,可以修改pm2配置文件,也生效。

结论

1、pm2可以在原有基础上新加实例,直接reload ecosystem.config.js即可启动新的实例。
2、通过pm2 delpm2 reload可以修改某个实例的所有内容(注:只是理论,笔者主要关注log、端口等参数)。
3、如果要一切复原,终极办法是使用pm2 kill