pm2的实现原理

# 简介

pm2基于cluster进行了封装,它能自动监控进程状态、重启进程、停止不稳定的进程(避免无限循环)等。

# 专用名词

pm2包括 Satan进程God Deamon守护进程进程间的远程调用rpccluster等几个概念:

  1. 如果不知道点西方文化,还真搞不清他的文件名为啥是 Satan 和 God:

撒旦(Satan),主要指《圣经》中的堕天使(也称堕天使撒旦), 他是反叛上帝耶和华的堕天使(Fallen Angels),曾经是上帝座前的天使,后来他因骄傲自大妄想与神同等而堕落成为魔鬼,被看作与上帝的力量相对的邪恶、黑暗之源。

简单的说Satan是破坏神,就是进程的异常退出、kill等;God是守护神,保护进程、重启进程等。

Satan.js提供了程序的退出、杀死等方法,因此它是魔鬼; God.js 负责维护进程的正常运行,当有异常退出时能保证重启,所以它是上帝。 God进程启动后一直运行,它相当于cluster中的Master进程,守护者worker进程的正常运行。

pm2的 RPC基本框架。Client与Daemon是采用了RPC进行通讯。

rpc(Remote Procedure Call Protocol)是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。同一机器不同进程间的方法调用也属于rpc的作用范畴。 代码中采用了axon-rpc (opens new window)axon (opens new window) 两个库,基本原理是提供服务的server绑定到一个域名和端口下,调用服务的client连接端口实现rpc连接。 后续新版本采用了pm2-axon-rpc (opens new window)pm2-axon (opens new window)两个库,绑定的方法也由端口变成.sock文件,因为采用port可能会和现有进程的端口产生冲突。

# God的进程运行模式

一般开发环境用fork模式,生产环境使用cluster模式

  • Cluster集群模式
  • Fork模式

# 实现

# 源码结构

# 执行流程

每次命令行的输入都会执行一次satan程序。如果God进程不在运行,首先需要启动God进程。然后根据指令,satan通过rpc调用God中对应的方法执行相应的逻辑。 以 pm2 start app.js -i 4为例,God在初次执行时会配置cluster,同时监听cluster中的事件:

// 配置cluster
cluster.setupMaster({
  exec : path.resolve(path.dirname(module.filename), 'ProcessContainer.js')
});

// 监听cluster事件
(function initEngine() {
  cluster.on('online', function(clu) {
    // worker进程在执行
    God.clusters_db[clu.pm_id].status = 'online';
  });

  // 命令行中 kill pid 会触发exit事件,process.kill不会触发exit
  cluster.on('exit', function(clu, code, signal) {
    // 重启进程 如果重启次数过于频繁直接标注为stopped
    God.clusters_db[clu.pm_id].status = 'starting';
    // 逻辑
    ...
  });
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

在God启动后, 会建立Satan和God的rpc链接,然后调用prepare方法。prepare方法会调用cluster.fork,完成集群的启动:

God.prepare = function(opts, cb) {
  ...
  return execute(opts, cb);
};
function execute(env, cb) {
  ...
  var clu = cluster.fork(env);
  ...
  God.clusters_db[id] = clu;

  clu.once('online', function() {
    God.clusters_db[id].status = 'online';
    if (cb) return cb(null, clu);
    return true;
  });

  return clu;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  1. Client启动关联Daemon
   daemon.innerStart(function() {
   
   KMDaemon.launchAndInteract(that.conf, {
   machine_name : that.machine_name,
   public_key : that.public_key,
   secret_key : that.secret_key
   
   }, function(err, data, interactor_proc) {
   
   that.interactor_process = interactor_proc;
   
   });
   
   that.launchRPC(function(err, meta) {
   
   return cb(null, {
   daemon_mode : that.conf.daemon_mode,
   new_pm2_instance : false,
   rpc_socket_file : that.rpc_socket_file,
   pub_socket_file : that.pub_socket_file,
   pm2_home : that.pm2_home
   });
   
   });
   
   });
   
   child.once(‘message’, function(msg) {
   
   debug(PM2 daemon launched with return message:, msg);
   
   child.removeListener(‘error’, onError);
   
   child.disconnect();
   
   if (opts && opts.interactor == false)
   
   return cb(null, child);
   
   /**
   
   * Here the Keymetrics agent is launched automaticcaly if
   
   * it has been already configured before (via pm2 link)
   
   */
   
   KMDaemon.launchAndInteract(that.conf, {
   
   machine_name : that.machine_name,
   
   public_key : that.public_key,
   
   secret_key : that.secret_key
   
   }, function(err, data, interactor_proc) {
   
   that.interactor_process = interactor_proc;
   
   return cb(null, child);
   
   });
   
   });
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
  1. Daemon有start,stop,close,kill进程的方法,与God的事件发射器(EventEmitter)关联
God.bus.on(‘axm:monitor’, function axmMonitor(msg) {

if (!msg.process)

return console.error([axm:monitor] no process defined’);

if (!msg.process || !God.clusters_db[msg.process.pm_id])

return console.error(‘Unknown id %s’, msg.process.pm_id);

util._extend(God.clusters_db[msg.process.pm_id].pm2_env.axm_monitor, Utility.clone(msg.data));

msg = null;

});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. Satan.js 3.1. Ping进程是否活着或者关闭
Satan.pingDaemon = function pingDaemon(cb) {
var req = axon.socket(‘req’);
var client = new rpc.Client(req);

debug([PING PM2] Trying to connect to server’);

client.sock.once(‘reconnect attempt’, function() {
client.sock.close();

debug(‘Daemon not launched’);
process.nextTick(function() {

return cb(false);
});

});

client.sock.once(‘connect’, function() {
client.sock.once(‘close’, function() {

return cb(true);

});

client.sock.close();

debug(‘Daemon alive’);

});

req.connect(cst.DAEMON_RPC_PORT);

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

3.2. 通知God

Satan.notifyGod = function(action_name, id, cb) {

Satan.executeRemote(‘notifyByProcessId’, {
id : id,
action_name : action_name,
manually : true
}, function() {

debug(‘God notified’);
return cb ? cb() : false;
});
};
1
2
3
4
5
6
7
8
9
10
11
12
  1. God是事件监听
var God = module.exports = {

next_id : 0,

clusters_db : {},

bus : new EventEmitter2({

wildcard: true,

delimiter::,

maxListeners: 1000

})

};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  1. God的监听进程方法有
God.ping

God.notifyKillPM2

God.duplicateProcessId

God.startProcessId

God.stopProcessId

God.resetMetaProcessId

God.deleteProcessId

God.restartProcessId

God.restartProcessName

God.sendSignalToProcessId

God.sendSignalToProcessName

God.stopWatch

God.toggleWatch

God.startWatch

God.reloadLogs

God.sendDataToProcessId

God.msgProcess

God.getVersion
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

# 问题

在想为啥pm2没有master进程

后来读了源码才知道它的master进程就是Deamon进程,而且进程名字也做了修改。由于ps aux | grep ** 这个指令对大小写敏感,所以要大写的PM才能找到:

在linux中,通过 ps aux | grep node 可以看到运行中的node进程:

ps aux | grep node PM2

pm2也是采用cluster.fork实现的集群,这也就是所谓的万变不离其宗。由于God Deamon这个Master进程一直执行,可以保证对每一个子进程监听事件 (opens new window),从而进行相应的操作。

# 参考链接

https://www.cnblogs.com/todosomeone/p/6044093.html

上次更新: 2022/04/15, 05:41:31
×