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

pomelo之master服务器的启动相关推荐

  1. redis-server启动但进程里没有_Redis——服务器的启动过程

    这一节,我们研究的目光聚焦在redis服务器的启动过程.具体研究启动过程做了哪些工作. 启动redis命令:src/redis-server redis.conf 30408:C 13 Jan 202 ...

  2. Linux下配置DNS服务器之一--Master服务器

    Linux下配置DNS服务器之一--Master服务器 系统环境: RedHat EL55 Oracle 11g RAC 集群中引入了SCAN(Single Client Access Name)的概 ...

  3. Redis服务器的启动过程分析

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/127.html?1455808771 本文将通过分析代码来介绍Redis的 ...

  4. pomelo分布式聊天服务器详解

    pomelo分布式聊天服务器详解 2014-01-05 11:43:49|  分类: node |  标签:pomelo  pomelo聊天  nodejs分布式聊天  pomelo分布式  |举报| ...

  5. 英雄远征Erlang源码分析(2)-网关服务器的启动

    上一篇文章解析了游戏源码的结构,我们知道该源码包含两个服务器的启动脚本:网关服务器和游戏服务器,其中网关服务器用于在玩家选择进入游戏服务器之前获取服务器列表,游戏服务器则处理玩家进入游戏服务器后的登录 ...

  6. sqlserver配置管理器不显示服务器,sql 2008配置管理器的服务器无法启动(待)

    当前位置:我的异常网» Sql Server » sql 2008配置管理器的服务器无法启动(待) sql 2008配置管理器的服务器无法启动(待) www.myexceptions.net  网友分 ...

  7. centos运行jar包需要的环境_Centos7服务器下启动jar包项目的最佳方法

    前言 在linux上运行jar包谁都会啊.为什么我还要单独拎出来讲呢.细心的朋友可能已经在标题中发现关键词Centos7和最佳方式. 这就说明我不是随便写点东西水一篇博客的ヾ(◍°∇°◍)ノ゙ 首先C ...

  8. 【转】“无法在Web服务器上启动调试。您不具备调试此应用程序的权限,此项目的URL位于Internet区域”错误提示的解决...

    错误提示: 无法在Web服务器上启动调试.您不具备调试此应用程序的权限,此项目的URL位于Internet区域 一般用下面的方法可以解决: 1:确认在"配置属性"中的"启 ...

  9. 无法在WEB服务器上启动调试,Web 服务器配置不正确

    访问IIS元数据库失败  思考可能是次序出了问题,解决  1.打开CMD,进入 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727  2.输入 aspnet_r ...

  10. 04_Weblogic之受管服务器:配置受管服务器,启动受管服务器,解决因为强制关闭Weblogic之后导致启动有问题的问题,配置boot.properties

     配置受管服务器, 先启动WebLogic服务器,启动方式如下: 在WebLogic控制台中的"开发模式"---"锁定并编辑"模式下,点击"Ser ...

最新文章

  1. 如果一栋楼起火谁赔偿_南昌一居民楼起火,短短几分钟里三四十人上演“救火大合唱”...
  2. 路由 交换 网桥 相关转贴
  3. SAP S/4HANA销售订单创建时,会自动触发生产订单的创建 1
  4. 双百双新产业项目是什么_投资380亿,广西38个“双百双新”产业项目开竣工
  5. 读ImageCropper源码
  6. 26元买4500斤脐橙,农民淘宝店被主播带头薅亏700万,“羊毛党”太狠了!
  7. VB 断开指定进程网络连接函数
  8. cad 打开硬件加速卡_CAD:“你的图纸缺少shx字体!”不知道该怎么办?不存在的!...
  9. Linux常用命令大全——赶紧收藏
  10. 自己收藏整理的一些操作系统资源
  11. 三维扫描仪为媒,虚拟试衣间下嫁普通制衣生产厂家
  12. MATLAB算法实战应用案例精讲-【智能优化算法】天牛须搜索-BAS (附MATLAB、C++以及Python源码)
  13. android 怎样设置铃声
  14. 分享45个Android实例源码
  15. 硬核FutureTask解析
  16. c语言中7行7列星号怎么做,C语言*星号的秘密
  17. Android权限申请
  18. Python爬虫爬取天天基金网
  19. HaaS100低功耗蓝牙体验
  20. 管理系列:项目管理之项目汇报总结

热门文章

  1. 无锡工艺技术计算机信息管理论文,无锡工艺职业技术学院05/06学年第一学期.doc...
  2. Linux嵌入式系统的电子相册代码,基于嵌入式Linux和Qt编程实现数码相框的设计
  3. Element UI中的Descriptions描述列表
  4. 英雄联盟lol鼠标突然不能a兵了
  5. 爬虫基础篇之斗鱼弹幕
  6. JM8.5中的高精度象素运动估计 1
  7. Pop猫 回收站图标
  8. Sample larger than population or is negative
  9. 中文.com域名如何申请 什么是.com域名过期
  10. 阿里云服务器搭建及域名申请攻略