pomelo入门04

写完前面的两篇文章,一直走的都是master服务器的流程,那么这一篇就真正涉及到master服务器的启动过程了,在真正开始之前,先回顾一下前面的两篇文章。。

(1)创建app的过程,这部分主要要完成的功能是读入用户定义的配置参数,并保存和处理这些配置参数。

(2)启动app的过程,这部分主要要完成的功能是load组件,完成对组件的包装(前面已经对master组件进行了说明,在真正的master外面还有一个master的包装,代理它的启动等过程)

新来看看那个master的代理吧:

/**
 * Component for master.
 */
var Master = require('../master/master');

/**
 * Component factory function
 *
 * @param  {Object} app  current application context
 * @return {Object}      component instances
 */
 //相当于require之后得到的就是这个函数
module.exports = function (app) {
  return new Component(app);
};

/**
* Master component class
*
* @param {Object} app  current application context
*/
var Component = function (app) {
  this.master = new Master(app);   //创建爱你master
};

var pro = Component.prototype;

/**
 * Component lifecycle function
 *
 * @param  {Function} cb
 * @return {Void}
 */
pro.start = function (cb) {
  this.master.start(cb);
};

/**
 * Component lifecycle function
 *
 * @param  {Boolean}   force whether stop the component immediately
 * @param  {Function}  cb
 * @return {Void}
 */
pro.stop = function (force, cb) {
  this.master.stop(cb);
};

就像前面说的一样,它就是一个外包装而已。。。

那么我们来看真正的master,首先来看看真正的master的构造:

//用于创建master
var Server = function(app) {
  this.app = app;
  this.masterInfo = app.getMaster();   //master的配置实在loadMaster的配置的时候读取的,这个时候获取master的配置
  this.registered = {};   //
  this.modules = [];  //所有的模块

  //Default is the main master
  this.isSlave = false;

  this.masterConsole = admin.createMasterConsole({   //创建console
    port: this.masterInfo.port   //master的端口
  });

  if(app.enabled('masterHA')){ //Zookeeper这部分暂时先搁置,因为以前完全不了解它
    this.zookeeper = Zookeeper.getClient(app);
  }
};

珍格格构造的过程看起来还是比较的简单,这里对于zookeeper的部分就先搁置,以后再来看,因为以前完全没有涉及到过zookeeper。

前面的代码要是读入master的配置信息,然后创建master的console,那么接下来来看这个console是怎么创建的吧:

module.exports.createMasterConsole = function(opts) {
	opts = opts || {};
	opts.master = true;   //用于表示当前是master服务器
	return new ConsoleService(opts);  //创建并返回console
};

好像这部分代码也没啥意思,好吧接着来看:

var ConsoleService = function(opts) {
	EventEmitter.call(this);   //让当前的对象可以处理事件
	this.port = opts.port;   //获取端口号
	this.values = {};
	this.master = opts.master;   //当前是否是master服务器

	this.modules = {};

	if(this.master) {
		this.agent = new MasterAgent(this);   //构造master agent
	} else {
		this.type = opts.type;
		this.id = opts.id;
		this.host = opts.host;
		this.agent = new MonitorAgent({
			consoleService: this,
			id: this.id,
			type: this.type,
			info: opts.info
		});
	}
};

好像代码还是很简单,主要还是要创建MasterAgent,好吧,我们再来看它的创建过程吧:

var MasterAgent = function(consoleService) {
  EventEmitter.call(this);
  this.consoleService = consoleService;   //console
  this.server = null;
  this.idMap = {};
  this.typeMap = {};
  this.clients = {};
  this.reqId = 1;
  this.callbacks = {};
  this.state = ST_INITED;
};

util.inherits(MasterAgent, EventEmitter);

好吧,没啥意思,那么我们接下来来看看master的start的过程吧,希望能有更多有意义的东西。。。

那么接下来来看master的start函数吧,它的代码比较长一些,需要一部分一部分的进行分析:

Server.prototype.start = function(cb) {
  registerDefaultModules(this.app);   //注册默认的modules
  loadModules(this, this.masterConsole);   //载入module

  var self = this;
  this.masterConsole.start(function(err) {   //启动console
    if(err) {
      cb(err);
      return;
    }
    startModules(self.modules, function(err) {  //启动module
      if(err) {
        cb(err);
        return;
      }
      //If it is the back up, do note start server
      if(!self.app.enabled('masterHA')){
        logger.info('masterHA not enabled, start servers');
        starter.runServers(self.app);  //启动其余的server
        cb();
      }else{
        self.zookeeper.start(function(err, result){
          if(err){
            logger.error('start zookeeper failed! err : %j', err);
            cb(err);
            return;
          }
          self.zookeeper.getLock(function(err, result){
            if(err || !result){
              self.isSlave = true;
              self.zookeeper.on('onPromote', self.onPromote.bind(self));
              cb();
            }else{
              self.zookeeper.setData(self.masterInfo, function(err){
                if(err){
                  logger.error('set master info faild!');
                  cb(err);
                  return;
                }

                starter.runServers(self.app);
                cb();
              });
            }
          });
        });
      }
    });
  });

  this.masterConsole.on('disconnect', function(id, type, reason) {  //设置disconnect事件
    crashLogger.info(util.format('[%s],[%s],[%s],[%s]', type, id, Date.now(), reason || 'disconnect'));
    var server = self.app.getServerById(id);
    if(!!server && server['auto-restart'] === 'true') {
      self.app.addServers(server);
      starter.run(self.app, server, function(err) {
        if(err) {
          cb(new Error("could not restart " + server.serverId + err), null);
          return;
        }
      });
    }
  });

  this.masterConsole.on('register', function(record) {  //register事件
    starter.bindCpu(record.id, record.pid, record.host);
  });
};

其实大意已经很清楚了,首先register模块,然后load模块,接着启动上面创建的console,然后再启动模块,然后再启动用户定义的其余的server,最后还要设置一些事件的处理函数。。。。

好了,那么首先来看看module的register的过程吧:

var registerDefaultModules = function(app) {
  app.registerAdmin(require('../modules/watchdog'), {app: app, master: true});
  app.registerAdmin(require('../modules/console'), {app: app, starter: starter});
  if(app.enabled('systemMonitor')) {
    app.registerAdmin(admin.modules.systemInfo);
    app.registerAdmin(admin.modules.nodeInfo);
    app.registerAdmin(admin.modules.monitorLog, {path: pathUtil.getLogPath(app.getBase())});
    app.registerAdmin(admin.modules.scripts, {app: app, path: pathUtil.getScriptPath(app.getBase())});
    if(os.platform() !== 'win32') {
      app.registerAdmin(admin.modules.profiler, {isMaster: true});
    }
  }
};

好像也没有什么意思吧,看看:

Application.registerAdmin = function(moduleId, module, opts){
  var modules = this.get('__modules__');
  if(!modules) {
    modules = [];
    this.set('__modules__', modules);
  }

  if(typeof moduleId !== 'string') {
    opts = module;   //这个是传进的参数
    module = moduleId;
    moduleId = module.moduleId;
  }

  modules.push({moduleId: moduleId, module: module, opts: opts});  //将它push进去
};

这部分其实还是相对比较的简单吧,无非是载入模块,并设置待会会用到的参数,那么接下来:

var loadModules = function(self, consoleService) {
  // load app register modules
  var modules = self.app.get('__modules__'); //获取module的信息

  if(!modules) {
    return;
  }

  var record, moduleId, module;
  for(var i=0, l=modules.length; i<l; i++){   //遍历所有的module
    record = modules[i];
    if(typeof record.module === 'function') {  //一般情况下,这里都是一个函数,因为module的定义直接弄成了一个函数,可以看成构造函数
      module = record.module(record.opts, consoleService);//可以看成调用module的构造函数
    } else {
      module = record.module;
    }

    moduleId = record.moduleId || module.moduleId;

    if(!moduleId) {
      logger.warn('ignore an uname module.');
      continue;
    }

    consoleService.register(moduleId, module);  //在console里面注册module,在console里面会将id和module关联起来保存
    self.modules.push(module);
  }
};

其实这部分的代码还是很简单的,首先遍历所有的module,然后调用构造函数,创建这些module,最后还要讲这些module注册到console里面去,在console里面会将这些module与其的id关联起来进行保存。。。

那么这里module的register和load过程基本就差不多了,至于说这些module有什么用,还是留到以后涉及到了再说吧。。。

好吧,我们接下来来看看console的启动过程:

ConsoleService.prototype.start = function(cb) {
	if(this.master) {
		this.agent.listen(this.port);  //监听端口
		exportEvent(this, this.agent, 'register');  //如果agent发生了register事件,那么这里也要调用一次
		exportEvent(this, this.agent, 'disconnect');
		process.nextTick(function() {
			utils.invokeCallback(cb);  //调用回调函数
		});
	} else {
		logger.info('try to connect master: %j, %j, %j', this.type, this.host, this.port);
		this.agent.connect(this.port, this.host, cb);
		exportEvent(this, this.agent, 'close');
	}

	exportEvent(this, this.agent, 'error');

	for(var mid in this.modules) {
		this.enable(mid);  //遍历并enable当前所有保存的module,在master的loadmodule的过程,会将这些module保存到console里面来
	}
};

这里首先会让agent来listen用户配置的master端口(socket.io),这里还有比较有意思的地方就是设置了一些事件,如果agent发生了,那么console相应的也会发生一次,类似javascript的DOM事件的冒泡的过程,从里面冒到外面来。。。估计这种设计想法也是有模仿的意思吧。。。接着在调用定义的回调函数,在回调函数中将会启动module和其余的server。。。这与说agent里的listen究竟干了些什么事情,这里也就先搁着一下吧,因为现在的所有看到的执行流程中还是没有设计到这部分。。。。

好了接下来来看看module的start过程吧:

var startModules = function(modules, cb) {
  // invoke the start lifecycle method of modules
  if(!modules) {
    return;
  }

  startModule(null, modules, 0, cb);
};

var startModule = function(err, modules, index, cb) {
  if(err || index >= modules.length) {
    cb(err);
    return;
  }

  var module = modules[index];
  if(module && typeof module.start === 'function') {
    module.start(function(err) {
      startModule(err, modules, index + 1, cb);  //我晕,这里居然还是递归的进行start
    });
  } else {
    startModule(err, modules, index + 1, cb);
  }
};

好像也没啥意思吧,说白了就是调用module的start函数,然后这里有个比较有意思的就是,start的过程居然还要递归的进行,至于说为什么这样,呵呵,不知道。。

那么接下来来看是怎么进行其余的server的启动吧:

 starter.runServers = function(app) {
  var servers = app.getServersFromConfig();
  for (var serverId in servers) {  //遍历所有的server
    this.run(app, servers[serverId]);  //启动server
  }
};

遍历所有的server,然后启动,这里server的信息说白了就是用户定义的server,这些信息在前面已经载入了进来。。

starter.run = function(app, server, cb) {
  env = app.get('env');  //当前环境,development或者production
  var cmd, key;
  if (isLocal(server.host)) {  //host配置信息
    var options = [];
    if (!!server.args) {
      if(typeof server.args === 'string') {
        options.push(server.args.trim());
      } else {
        options.push(server.args);
      }
    }
    cmd = app.get('main');  //用于启动给的主要信息
      /*{ main: '/home/fjs/Desktop/pomelo/game-server/app.js',
  env: 'development',
  id: 'connector-server-1',
  host: '127.0.0.1',
  port: 4050,
  clientPort: 3050,
  frontend: 'true',
  serverType: 'connector' }*/
    options.push(cmd);
    options.push(util.format('env=%s',  env));  //当前的环境
    for(key in server) {  //将server的配置信息输入到命令启动行,例如host,port等
      if(key === 'cpu') {
        cpus[server['id']] = server[key];
      }
      options.push(util.format('%s=%s', key, server[key]));
    }
    starter.localrun(process.execPath, null, options, cb);  //运行命令行
  } else {
    cmd = util.format('cd "%s" && "%s"', app.getBase(), process.execPath);
    var arg = server.args;
    if (arg !== undefined) {
      cmd += arg;
    }
    cmd += util.format(' "%s" env=%s ', app.get('main'), env);
    for(key in server) {
      if(key === 'cpu') {
        cpus[server['id']] = server[key];
      }
      cmd += util.format(' %s=%s ', key, server[key]);
    }
    starter.sshrun(cmd, server.host, cb);
  }
};

这部分代码仿佛看起来挺复杂的,其实不然,因为大多数在前面的代码中都有涉及,无非是将要执行的命令行处理出来,然后待会用这些命令行参数来进行启动。。。那么就不细说了,直接来看localrun函数吧:

 //直接启动命令行
starter.localrun = function (cmd, host, options, callback) {
  logger.info('Executing ' + cmd + ' ' + options + ' locally');
  spawnProcess(cmd, host, options, callback);
};

那么其余server的启动也就差不多了,当然这部分还有一个插曲,那就是这里server的启动还需要分时本地服务器的,还是外地服务器的,其实看代码也就启动的过程有稍微的区别,别的也都差不多,就不细说了。。。

好了,那么整个master的启动过程大概就如下:

(1)创建masterconsole

(2)创建masteragent

(3)注册以及载入module

(4)启动console
(5)启动module

(6)启动其余的server

这里有一张图感觉应该能形容master的构成:

其实也就是说master服务器大多数的功能都是通过masterconsole进行的,而masterconsole又包含一个masteragent用于监听端口,以及一些处理。。

当然至于说master服务器的具体运行原理这里文章中并没有涉及,以后会补上,因为现在确实还不知道master干些什么事情。

发布了39 篇原创文章 · 获赞 81 · 访问量 35万+

猜你喜欢

转载自blog.csdn.net/qq_31967569/article/details/105291428