写完前面的两篇文章,一直走的都是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干些什么事情。。。