pomelo之master服务器的启动

来源:互联网 发布:js 日期时间差 编辑:程序博客网 时间:2024/05/14 17:31


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

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

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


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

[javascript] view plaincopyprint?
  1. /** 
  2.  * Component for master. 
  3.  */  
  4. var Master = require('../master/master');  
  5.   
  6. /** 
  7.  * Component factory function 
  8.  * 
  9.  * @param  {Object} app  current application context 
  10.  * @return {Object}      component instances 
  11.  */  
  12.  //相当于require之后得到的就是这个函数  
  13. module.exports = function (app) {  
  14.   return new Component(app);  
  15. };  
  16.   
  17. /** 
  18. * Master component class 
  19. * 
  20. * @param {Object} app  current application context 
  21. */  
  22. var Component = function (app) {  
  23.   this.master = new Master(app);   //创建爱你master  
  24. };  
  25.   
  26. var pro = Component.prototype;  
  27.   
  28. /** 
  29.  * Component lifecycle function 
  30.  * 
  31.  * @param  {Function} cb 
  32.  * @return {Void} 
  33.  */  
  34. pro.start = function (cb) {  
  35.   this.master.start(cb);  
  36. };  
  37.   
  38. /** 
  39.  * Component lifecycle function 
  40.  * 
  41.  * @param  {Boolean}   force whether stop the component immediately 
  42.  * @param  {Function}  cb 
  43.  * @return {Void} 
  44.  */  
  45. pro.stop = function (force, cb) {  
  46.   this.master.stop(cb);  
  47. };  
就像前面说的一样,它就是一个外包装而已。。。

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

[javascript] view plaincopyprint?
  1. //用于创建master  
  2. var Server = function(app) {  
  3.   this.app = app;  
  4.   this.masterInfo = app.getMaster();   //master的配置实在loadMaster的配置的时候读取的,这个时候获取master的配置  
  5.   this.registered = {};   //  
  6.   this.modules = [];  //所有的模块  
  7.   
  8.   //Default is the main master  
  9.   this.isSlave = false;  
  10.   
  11.   this.masterConsole = admin.createMasterConsole({   //创建console  
  12.     port: this.masterInfo.port   //master的端口  
  13.   });  
  14.   
  15.   if(app.enabled('masterHA')){ //Zookeeper这部分暂时先搁置,因为以前完全不了解它  
  16.     this.zookeeper = Zookeeper.getClient(app);  
  17.   }  
  18. };  
珍格格构造的过程看起来还是比较的简单,这里对于zookeeper的部分就先搁置,以后再来看,因为以前完全没有涉及到过zookeeper。

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

[javascript] view plaincopyprint?
  1. module.exports.createMasterConsole = function(opts) {  
  2.     opts = opts || {};  
  3.     opts.master = true;   //用于表示当前是master服务器  
  4.     return new ConsoleService(opts);  //创建并返回console  
  5. };  
好像这部分代码也没啥意思,好吧接着来看:
[javascript] view plaincopyprint?
  1. var ConsoleService = function(opts) {  
  2.     EventEmitter.call(this);   //让当前的对象可以处理事件  
  3.     this.port = opts.port;   //获取端口号  
  4.     this.values = {};  
  5.     this.master = opts.master;   //当前是否是master服务器  
  6.   
  7.     this.modules = {};  
  8.   
  9.     if(this.master) {  
  10.         this.agent = new MasterAgent(this);   //构造master agent  
  11.     } else {  
  12.         this.type = opts.type;  
  13.         this.id = opts.id;  
  14.         this.host = opts.host;  
  15.         this.agent = new MonitorAgent({  
  16.             consoleService: this,  
  17.             id: this.id,  
  18.             type: this.type,  
  19.             info: opts.info  
  20.         });  
  21.     }  
  22. };  
好像代码还是很简单,主要还是要创建MasterAgent,好吧,我们再来看它的创建过程吧:
[javascript] view plaincopyprint?
  1. var MasterAgent = function(consoleService) {  
  2.   EventEmitter.call(this);  
  3.   this.consoleService = consoleService;   //console  
  4.   this.server = null;  
  5.   this.idMap = {};  
  6.   this.typeMap = {};  
  7.   this.clients = {};  
  8.   this.reqId = 1;  
  9.   this.callbacks = {};  
  10.   this.state = ST_INITED;  
  11. };  
  12.   
  13. util.inherits(MasterAgent, EventEmitter);  
好吧,没啥意思,那么我们接下来来看看master的start的过程吧,希望能有更多有意义的东西。。。

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

[javascript] view plaincopyprint?
  1. Server.prototype.start = function(cb) {  
  2.   registerDefaultModules(this.app);   //注册默认的modules  
  3.   loadModules(thisthis.masterConsole);   //载入module  
  4.   
  5.   var self = this;  
  6.   this.masterConsole.start(function(err) {   //启动console  
  7.     if(err) {  
  8.       cb(err);  
  9.       return;  
  10.     }  
  11.     startModules(self.modules, function(err) {  //启动module  
  12.       if(err) {  
  13.         cb(err);  
  14.         return;  
  15.       }  
  16.       //If it is the back up, do note start server  
  17.       if(!self.app.enabled('masterHA')){  
  18.         logger.info('masterHA not enabled, start servers');  
  19.         starter.runServers(self.app);  //启动其余的server  
  20.         cb();  
  21.       }else{  
  22.         self.zookeeper.start(function(err, result){  
  23.           if(err){  
  24.             logger.error('start zookeeper failed! err : %j', err);  
  25.             cb(err);  
  26.             return;  
  27.           }  
  28.           self.zookeeper.getLock(function(err, result){  
  29.             if(err || !result){  
  30.               self.isSlave = true;  
  31.               self.zookeeper.on('onPromote', self.onPromote.bind(self));  
  32.               cb();  
  33.             }else{  
  34.               self.zookeeper.setData(self.masterInfo, function(err){  
  35.                 if(err){  
  36.                   logger.error('set master info faild!');  
  37.                   cb(err);  
  38.                   return;  
  39.                 }  
  40.   
  41.                 starter.runServers(self.app);  
  42.                 cb();  
  43.               });  
  44.             }  
  45.           });  
  46.         });  
  47.       }  
  48.     });  
  49.   });  
  50.   
  51.   this.masterConsole.on('disconnect'function(id, type, reason) {  //设置disconnect事件  
  52.     crashLogger.info(util.format('[%s],[%s],[%s],[%s]', type, id, Date.now(), reason || 'disconnect'));  
  53.     var server = self.app.getServerById(id);  
  54.     if(!!server && server['auto-restart'] === 'true') {  
  55.       self.app.addServers(server);  
  56.       starter.run(self.app, server, function(err) {  
  57.         if(err) {  
  58.           cb(new Error("could not restart " + server.serverId + err), null);  
  59.           return;  
  60.         }  
  61.       });  
  62.     }  
  63.   });  
  64.   
  65.   this.masterConsole.on('register'function(record) {  //register事件  
  66.     starter.bindCpu(record.id, record.pid, record.host);  
  67.   });  
  68. };  
其实大意已经很清楚了,首先register模块,然后load模块,接着启动上面创建的console,然后再启动模块,然后再启动用户定义的其余的server,最后还要设置一些事件的处理函数。。。。

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

[javascript] view plaincopyprint?
  1. var registerDefaultModules = function(app) {  
  2.   app.registerAdmin(require('../modules/watchdog'), {app: app, master: true});  
  3.   app.registerAdmin(require('../modules/console'), {app: app, starter: starter});  
  4.   if(app.enabled('systemMonitor')) {  
  5.     app.registerAdmin(admin.modules.systemInfo);  
  6.     app.registerAdmin(admin.modules.nodeInfo);  
  7.     app.registerAdmin(admin.modules.monitorLog, {path: pathUtil.getLogPath(app.getBase())});  
  8.     app.registerAdmin(admin.modules.scripts, {app: app, path: pathUtil.getScriptPath(app.getBase())});  
  9.     if(os.platform() !== 'win32') {  
  10.       app.registerAdmin(admin.modules.profiler, {isMaster: true});  
  11.     }  
  12.   }  
  13. };  
好像也没有什么意思吧,看看:
[javascript] view plaincopyprint?
  1. Application.registerAdmin = function(moduleId, module, opts){  
  2.   var modules = this.get('__modules__');  
  3.   if(!modules) {  
  4.     modules = [];  
  5.     this.set('__modules__', modules);  
  6.   }  
  7.   
  8.   if(typeof moduleId !== 'string') {  
  9.     opts = module;   //这个是传进的参数  
  10.     module = moduleId;  
  11.     moduleId = module.moduleId;  
  12.   }  
  13.   
  14.   modules.push({moduleId: moduleId, module: module, opts: opts});  //将它push进去  
  15. };  
这部分其实还是相对比较的简单吧,无非是载入模块,并设置待会会用到的参数,那么接下来:
[javascript] view plaincopyprint?
  1. var loadModules = function(self, consoleService) {  
  2.   // load app register modules  
  3.   var modules = self.app.get('__modules__'); //获取module的信息  
  4.   
  5.   if(!modules) {  
  6.     return;  
  7.   }  
  8.   
  9.   var record, moduleId, module;  
  10.   for(var i=0, l=modules.length; i<l; i++){   //遍历所有的module  
  11.     record = modules[i];  
  12.     if(typeof record.module === 'function') {  //一般情况下,这里都是一个函数,因为module的定义直接弄成了一个函数,可以看成构造函数  
  13.       module = record.module(record.opts, consoleService);//可以看成调用module的构造函数  
  14.     } else {  
  15.       module = record.module;  
  16.     }  
  17.   
  18.     moduleId = record.moduleId || module.moduleId;  
  19.   
  20.     if(!moduleId) {  
  21.       logger.warn('ignore an uname module.');  
  22.       continue;  
  23.     }  
  24.   
  25.     consoleService.register(moduleId, module);  //在console里面注册module,在console里面会将id和module关联起来保存  
  26.     self.modules.push(module);  
  27.   }  
  28. };  
其实这部分的代码还是很简单的,首先遍历所有的module,然后调用构造函数,创建这些module,最后还要讲这些module注册到console里面去,在console里面会将这些module与其的id关联起来进行保存。。。

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


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

[javascript] view plaincopyprint?
  1. ConsoleService.prototype.start = function(cb) {  
  2.     if(this.master) {  
  3.         this.agent.listen(this.port);  //监听端口  
  4.         exportEvent(thisthis.agent, 'register');  //如果agent发生了register事件,那么这里也要调用一次  
  5.         exportEvent(thisthis.agent, 'disconnect');  
  6.         process.nextTick(function() {  
  7.             utils.invokeCallback(cb);  //调用回调函数  
  8.         });  
  9.     } else {  
  10.         logger.info('try to connect master: %j, %j, %j'this.type, this.host, this.port);  
  11.         this.agent.connect(this.port, this.host, cb);  
  12.         exportEvent(thisthis.agent, 'close');  
  13.     }  
  14.   
  15.     exportEvent(thisthis.agent, 'error');  
  16.   
  17.     for(var mid in this.modules) {  
  18.         this.enable(mid);  //遍历并enable当前所有保存的module,在master的loadmodule的过程,会将这些module保存到console里面来  
  19.     }  
  20. };  
这里首先会让agent来listen用户配置的master端口(socket.io),这里还有比较有意思的地方就是设置了一些事件,如果agent发生了,那么console相应的也会发生一次,类似javascript的DOM事件的冒泡的过程,从里面冒到外面来。。。估计这种设计想法也是有模仿的意思吧。。。接着在调用定义的回调函数,在回调函数中将会启动module和其余的server。。。这与说agent里的listen究竟干了些什么事情,这里也就先搁着一下吧,因为现在的所有看到的执行流程中还是没有设计到这部分。。。。


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

[javascript] view plaincopyprint?
  1. var startModules = function(modules, cb) {  
  2.   // invoke the start lifecycle method of modules  
  3.   if(!modules) {  
  4.     return;  
  5.   }  
  6.   
  7.   startModule(null, modules, 0, cb);  
  8. };  
  9.   
  10. var startModule = function(err, modules, index, cb) {  
  11.   if(err || index >= modules.length) {  
  12.     cb(err);  
  13.     return;  
  14.   }  
  15.   
  16.   var module = modules[index];  
  17.   if(module && typeof module.start === 'function') {  
  18.     module.start(function(err) {  
  19.       startModule(err, modules, index + 1, cb);  //我晕,这里居然还是递归的进行start  
  20.     });  
  21.   } else {  
  22.     startModule(err, modules, index + 1, cb);  
  23.   }  
  24. };  
好像也没啥意思吧,说白了就是调用module的start函数,然后这里有个比较有意思的就是,start的过程居然还要递归的进行,至于说为什么这样,呵呵,不知道。。


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

[javascript] view plaincopyprint?
  1. starter.runServers = function(app) {  
  2.  var servers = app.getServersFromConfig();  
  3.  for (var serverId in servers) {  //遍历所有的server  
  4.    this.run(app, servers[serverId]);  //启动server  
  5.  }  
  6. ;  
遍历所有的server,然后启动,这里server的信息说白了就是用户定义的server,这些信息在前面已经载入了进来。。

[javascript] view plaincopyprint?
  1. starter.run = function(app, server, cb) {  
  2.   env = app.get('env');  //当前环境,development或者production  
  3.   var cmd, key;  
  4.   if (isLocal(server.host)) {  //host配置信息  
  5.     var options = [];  
  6.     if (!!server.args) {  
  7.       if(typeof server.args === 'string') {  
  8.         options.push(server.args.trim());  
  9.       } else {  
  10.         options.push(server.args);  
  11.       }  
  12.     }  
  13.     cmd = app.get('main');  //用于启动给的主要信息  
  14.       /*{ main: '/home/fjs/Desktop/pomelo/game-server/app.js', 
  15.   env: 'development', 
  16.   id: 'connector-server-1', 
  17.   host: '127.0.0.1', 
  18.   port: 4050, 
  19.   clientPort: 3050, 
  20.   frontend: 'true', 
  21.   serverType: 'connector' }*/  
  22.     options.push(cmd);  
  23.     options.push(util.format('env=%s',  env));  //当前的环境  
  24.     for(key in server) {  //将server的配置信息输入到命令启动行,例如host,port等  
  25.       if(key === 'cpu') {  
  26.         cpus[server['id']] = server[key];  
  27.       }  
  28.       options.push(util.format('%s=%s', key, server[key]));  
  29.     }  
  30.     starter.localrun(process.execPath, null, options, cb);  //运行命令行  
  31.   } else {  
  32.     cmd = util.format('cd "%s" && "%s"', app.getBase(), process.execPath);  
  33.     var arg = server.args;  
  34.     if (arg !== undefined) {  
  35.       cmd += arg;  
  36.     }  
  37.     cmd += util.format(' "%s" env=%s ', app.get('main'), env);  
  38.     for(key in server) {  
  39.       if(key === 'cpu') {  
  40.         cpus[server['id']] = server[key];  
  41.       }  
  42.       cmd += util.format(' %s=%s ', key, server[key]);  
  43.     }  
  44.     starter.sshrun(cmd, server.host, cb);  
  45.   }  
  46. };  

这部分代码仿佛看起来挺复杂的,其实不然,因为大多数在前面的代码中都有涉及,无非是将要执行的命令行处理出来,然后待会用这些命令行参数来进行启动。。。那么就不细说了,直接来看localrun函数吧:
[javascript] view plaincopyprint?
  1.  //直接启动命令行  
  2. starter.localrun = function (cmd, host, options, callback) {  
  3.   logger.info('Executing ' + cmd + ' ' + options + ' locally');  
  4.   spawnProcess(cmd, host, options, callback);  
  5. };  

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


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

(1)创建masterconsole

(2)创建masteragent

(3)注册以及载入module

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

(6)启动其余的server


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


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

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

0 0
原创粉丝点击