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干些什么事情。