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

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

源码

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
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();

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

/*
用于测试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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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",
}
}
]
};

使用

启动服务端:

1
pm2 start

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

1
node client.js

测试使用的命令:

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

测试项

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

更新env中的字段值

可行。

如果修改的是env_production,则:

1
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配置文件。更好的方法请参考下面示例。

删除实例再启动

1
2
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

李迟 2019.2.25 周一中午