【pomelo】master服务和connector等服务的创建流程 -- !!! 很详细
发布于 4 天前 作者 gocpplua 155 次浏览 来自 分享

pomelo总体流程是下述四个阶段:

/**
 * Application states
 */
var STATE_INITED  = 1;  // app has inited -> Application.init 
var STATE_START = 2;  // app start ->  Application.start
var STATE_STARTED = 3;  // app has started -> Application.afterStart
var STATE_STOPED  = 4;  // app has stoped -> Application.stop

这个是插件加载流程:

RESERVED: {
...
    START: 'start',
    AFTER_START: 'afterStart',
...
  },

一、项目入口:app.js

每一个项目创建后,会生成入口:app.js

var pomelo = require('pomelo');
var app = pomelo.createApp();
app.set('name', '$');
...
// start app
app.start();
...

二、STATE_INITED

pomelo.createApp()中会进行初始化,然后设置Application.state = STATE_INITED;

三、STATE_START

接着执行app.start();。这里我们看到会进入appUtil.startByType,然后进入匿名回调。进入以后首先会加载组件,加载成功后,再去判断是否存在beforeFun:

var beforeFun = self.lifecycleCbs[Constants.LIFECYCLE.BEFORE_STARTUP];
    if(!!beforeFun) {
      beforeFun.call(null, self, startUp);
    } else {
      startUp();
    }

经过我的调试,发现是不存在beforeFun的(关于lifecycleCbs具体的分析见此文附录),于是就会进入到startUp:

    var startUp = function() {
      appUtil.optComponents(self.loaded, Constants.RESERVED.START, function(err) {
        self.state = STATE_START;
        if(err) {
          utils.invokeCallback(cb, err);
        } else {
          logger.info('%j enter after start...', self.getServerId());
          self.afterStart(cb);
        }
      });
    };

因为已经加载过相关组件,这边appUtil.optComponents用来运行组件的start函数:

Constants.RESERVED.START -> start

当所有的都执行完毕,进入回调。先设置self.state = STATE_START;,然后如果没有出错,就会执行:self.afterStart(cb);

四、STATE_STARTED

从上面步骤,先去执行组件的afterStart函数,所有组件都执行完毕后,进入回调:

self.state = STATE_STARTED;
    var id = self.getServerId();
    if(!err) {
      logger.info('%j finish start', id);
    }
    if(!!afterFun) {
      afterFun.call(null, self, function() {
        utils.invokeCallback(cb, err);
      });
    } else {
      utils.invokeCallback(cb, err);
    }
    var usedTime = Date.now() - self.startTime;
    logger.info('%j startup in %s ms', id, usedTime);
    self.event.emit(events.START_SERVER, id);

这里最后会发型一个START_SERVERmonitorwatcher.js

五、STATE_STOPED

Application.stop中会设置:

this.state = STATE_STOPED;

那么是谁调用Application.stop。 如下:

pomelo\lib\modules\console.js中Module.prototype.monitorHandler,msg.signal为'stop''restart'。 其他的就自己找一下吧。


六、child进程创建

上面是master服务器的启动流程,那么对于其他服务,其他服务器是在什么时候启动的呢? 我们先说下app.type的设置,这个在后面有用。 我们在Application.init中,调用appUtil.defaultConfiguration,然后在appUtil.processArgs中设置app.type = Constants.RESERVED.ALL;:

var processArgs = function(app, args) {
...
  var type = args.type || Constants.RESERVED.ALL;
  var startId = args.startId;

  app.set(Constants.RESERVED.MAIN, args.main, true);
  app.set(Constants.RESERVED.SERVER_TYPE, serverType, true);
  app.set(Constants.RESERVED.SERVER_ID, serverId, true);
  app.set(Constants.RESERVED.MODE, mode, true);
  app.set(Constants.RESERVED.TYPE, type, true);
...
};

回到主流程中,我们会调用各个组件的start函数,当我们在调用master组件时候:

// pomelo\lib\components\master.js
pro.start = function (cb) {
  this.master.start(cb);
};

上面的this.master.start,实际上是在调用:

// Epomelo\lib\master\master.js
Server.prototype.start = function(cb) {
  moduleUtil.registerDefaultModules(true, this.app, this.closeWatcher);
  moduleUtil.loadModules(this, this.masterConsole);

  var self = this;
  // start master console
  this.masterConsole.start(function(err) {
   // 其实是:node_modules\pomelo-admin\lib\consoleService.js
    if(err) {
      process.exit(0);
    }
    moduleUtil.startModules(self.modules, function(err) {
    // 经过几次回调以后,会进入这里!!!!
      if(err) {
        utils.invokeCallback(cb, err);
        return;
      }

      if(self.app.get(Constants.RESERVED.MODE) !== Constants.RESERVED.STAND_ALONE) {
        starter.runServers(self.app);
      }
      utils.invokeCallback(cb);
    });
  });
  
....
};

上面的start函数,最后会进入到moduleUtil.startModules的回调:

 if(err) {
        utils.invokeCallback(cb, err);
        return;
      }

      if(self.app.get(Constants.RESERVED.MODE) !== Constants.RESERVED.STAND_ALONE) {
        starter.runServers(self.app);
      }
      utils.invokeCallback(cb);

由于不是独立模式,会进入:starter.runServers(self.app);

// pomelo\lib\master\starter.js
/**
 * Run all servers
 *
 * @param {Object} app current application  context
 * @return {Void}
 */
 starter.runServers = function(app) {
  console.trace("runServer")
  var server, servers;
  var condition = app.startId || app.type;
  switch(condition) {
    case Constants.RESERVED.MASTER:
    break;
    case Constants.RESERVED.ALL:
    servers = app.getServersFromConfig();
    for (var serverId in servers) {
      this.run(app, servers[serverId]);
    }
    break;
    default:
    server = app.getServerFromConfig(condition);
    if(!!server) {
      this.run(app, server);
    } else {
      servers = app.get(Constants.RESERVED.SERVERS)[condition];
      for(var i=0; i<servers.length; i++) {
        this.run(app, servers[i]);
      }
    }
  }
};

这里我们的condition实际上就是Constants.RESERVED.ALL,就会执行:

// pomelo\lib\master\starter.js
    servers = app.getServersFromConfig();
    for (var serverId in servers) {
      this.run(app, servers[serverId]);
    }

上面就是获取servers.json中的配置,然后依次运行,我们看下run函数:

// pomelo\lib\master\starter.js
/**
 * Run server
 *
 * @param {Object} app current application context
 * @param {Object} server
 * @return {Void}
 */
starter.run = function(app, server, cb) {
  console.log(`run ${process.pid} `)
  env = app.get(Constants.RESERVED.ENV);
  var cmd, key;
  if (utils.isLocal(server.host)) {
...
    starter.localrun(process.execPath, null, options, cb);
  } else {
...
    starter.sshrun(cmd, server.host, cb);
  }
};

上面根据host,区分是不是本机地址,调用不同的接口:starter.localrunstarter.sshr.n。这两个函数,最后都会调用starter.spawnProcess:

/**
 * Fork child process to run command.
 *
 * @param {String} command
 * @param {Object} options
 * @param {Callback} callback
 *
 */
var spawnProcess = function(command, host, options, cb) {
  console.trace("spawnProcess", command, options)
  var child = null;

  if(env === Constants.RESERVED.ENV_DEV) {
    child = cp.spawn(command, options);
    ...
  } else {
    child = cp.spawn(command, options, {detached: true, stdio: 'inherit'});
    console.log(`spawnProcess3 ${process.pid} `, command, options)
    child.unref();
  }

 ...
};

上面最后就是调用nodejs的child_process模块的spawn函数,创建新进程:

var cp = require('child_process');
cp.spawn(command, options,...)

我调试了下,得到commandoption参数:

command:'D:\\Program Files\\nodejs\\node.exe'
option:
[
  "e:\\3rdparty\\pomelo_proj\\HelloWorld\\game-server\\app.js",
  "env=development",
  "id=connector-server-1",
  "host=127.0.0.1",
  "port=3150",
  "clientHost=127.0.0.1",
  "clientPort=3010",
  "frontend=true",
  "serverType=connector",
]

从中我们可以看出,其实有执行了一次app.js,只是现在的参数不一样了,例如:serverType

以上就是master进程和子进程的创建流程。


附录:

下面是npm-lifecycle相关的各个阶段

// pomelo\lib\util\constants.js
  LIFECYCLE: {
    BEFORE_STARTUP: 'beforeStartup', ->  Application.start
    BEFORE_SHUTDOWN: 'beforeShutdown', -> Application.stop
    AFTER_STARTUP: 'afterStartup', -> Application.afterStart
    AFTER_STARTALL: 'afterStartAll' -> monitorwatcher.js (startOver)
  },

我们从BEFORE_STARTUP开始,全文搜索后BEFORE_STARTUP,发现pomelo就一个地方使用:

Application.start = function(cb) {
...
  
  var self = this;
  appUtil.startByType(self, function() {
    appUtil.loadDefaultComponents(self);
    var startUp = function() {
...
    };
    var beforeFun = self.lifecycleCbs[Constants.LIFECYCLE.BEFORE_STARTUP]; // 此处
    if(!!beforeFun) {
      beforeFun.call(null, self, startUp);
    } else { // lifecycleCbs 不存在,所有都是执行startUp
      startUp();
    }
  });
};

然后看下self.lifecycleCbs:

// pomelo\lib\util\appUtil.js
/**
 * Load lifecycle file.
 *
 */
var loadLifecycle = function(app) {
  var filePath = path.join(app.getBase(), Constants.FILEPATH.SERVER_DIR, app.serverType, Constants.FILEPATH.LIFECYCLE);
  if(!fs.existsSync(filePath)) {
    return;
  }
  var lifecycle = require(filePath);
  for(var key in lifecycle) {
    if(typeof lifecycle[key] === 'function') {
      app.lifecycleCbs[key] = lifecycle[key];
    } else {
      logger.warn('lifecycle.js in %s is error format.', filePath);
    }
  }
};

我调试了一把,得到 filePath:

‘XXXXXXX\game-server\app\servers\master\lifecycle.js’

上面就是 master 服务器对应的 lifecycle.js 路径。不过没这个文件。所有我们的pomelo程序一直都是执行startUp。

我后来在自己电脑上全局搜索了下,在node源码路径找到:node-v14.16.1\deps\npm\lib\utils\lifecycle.js

exports = module.exports = runLifecycle

const lifecycleOpts = require('../config/lifecycle')
const lifecycle = require('npm-lifecycle')

function runLifecycle (pkg, stage, wd, moreOpts, cb) {
  if (typeof moreOpts === 'function') {
    cb = moreOpts
    moreOpts = null
  }

  const opts = lifecycleOpts(moreOpts)
  lifecycle(pkg, stage, wd, opts).then(cb, cb)
}

查看上面 lifecycleOpts 对应的文件:

'use strict'

const npm = require('../npm.js')
const log = require('npmlog')

module.exports = lifecycleOpts

let opts

function lifecycleOpts (moreOpts) {
  if (!opts) {
    opts = {
      config: npm.config.snapshot,
      dir: npm.dir,
      failOk: false,
      force: npm.config.get('force'),
      group: npm.config.get('group'),
      ignorePrepublish: npm.config.get('ignore-prepublish'),
      ignoreScripts: npm.config.get('ignore-scripts'),
      log: log,
      nodeOptions: npm.config.get('node-options'),
      production: npm.config.get('production'),
      scriptShell: npm.config.get('script-shell'),
      scriptsPrependNodePath: npm.config.get('scripts-prepend-node-path'),
      unsafePerm: npm.config.get('unsafe-perm'),
      user: npm.config.get('user')
    }
  }

  return moreOpts ? Object.assign({}, opts, moreOpts) : opts
}

想要了解 npm 中的npm-lifecycle,请戳链接

接下来我们回到: Application.start,进入到appUtil.startByType的匿名回调函数,进入到插件的start阶段。

回到顶部