pomelo中組件是可重用的服務(wù)單位,一個組件實例提供若干種服務(wù),比如說處理機組件載入后處理機代碼后將會把客戶端消息傳遞給請求處理機。
- pomelo是“微內(nèi)核+插件”的實現(xiàn)方式,由松耦合的組件構(gòu)成,每個組件完成特定的任務(wù)。
- pomelo的核心功能都是由組件完成的,整個pomelo框架可以看作是一個組件容器,完成組件的加載以及生命周期管理。
生命周期
- 每個組件都要定義
start、afterStart、stop等調(diào)用(hook鉤子函數(shù)),供pomelo管理其生命周期。 - 各個組件需要調(diào)用
cb回調(diào)函數(shù)來持續(xù)后續(xù)步驟 - 組件也可以傳入一個參數(shù)給
cb來表示當(dāng)前組件啟動失敗,以此來使應(yīng)用結(jié)束這個進(jìn)程。
| 鉤子函數(shù) | 生命周期 | 描述 |
|---|---|---|
| start(cb) | 組件開始階段 | 當(dāng)組件啟動時被調(diào)用 |
| afterStart(cb) | 組件聲明階段,當(dāng)組件啟動之后回調(diào) | 在當(dāng)前進(jìn)程所有被注冊組件啟動之后調(diào)用,給組件進(jìn)行協(xié)作兼初始化的機會。 |
| stop(cb) | 組件停止階段,當(dāng)組件停止時回調(diào) | 當(dāng)服務(wù)將要停止的時候調(diào)用 |
組件啟動流程:
- pomelo先調(diào)用加載的每個組件的
start(cb),當(dāng)全部調(diào)用完畢后才會去調(diào)用其加載的每個組件的afterStart(cb),注意按順序調(diào)用。 - 因為調(diào)用
afterStart(cb)時,所有組件的start(cb)已經(jīng)調(diào)用完畢,此時可添加些需全局就緒的操作。 -
stop(cb)用來程序結(jié)束時對組件進(jìn)行清理。可以做些清理作業(yè),比如沖刷此周期中的數(shù)據(jù)到數(shù)據(jù)庫。force參數(shù)若為true則表示所有組件都需要被立即停止。
例如:自定義組件
- 應(yīng)用配置中加載自定義組件并傳入?yún)?shù)
$ vim app.js
app.configure('production|development', 'master', function() {
app.load(require('./app/components/Hello'), {interval: 5000});
});
- 創(chuàng)建自定義組件以及生命周期函數(shù)
$ vim app/components/Hello.js
var DEFAULT_INTERVAL = 3000
//組件類
var Component = function(app, opts) {
this.app = app
this.interval = opts.interval | DEFAULT_INTERVAL
this.timerId = null
};
var pro = Component.prototype
//組件名稱
pro.name = '__Hello__'
//組件生命周期鉤子函數(shù)
pro.start = function(cb) {
console.log('Component Start')
var self = this
this.timerId = setInterval(function() {
console.log(self.app.getServerId() + ": Component!");
}, this.interval);
process.nextTick(cb)
}
pro.afterStart = function (cb) {
console.log('Component afterStart');
process.nextTick(cb)
}
pro.stop = function(force, cb) {
cosole.log('Component stop')
clearInterval(this.timerId)
process.nextTick(cb)
}
// 對外導(dǎo)出工廠函數(shù)
module.exports = function(app, opts) {
return new Component(app, opts)
}
定義組件
定義組件時一般會向外導(dǎo)出一個工廠函數(shù),注意不是對象。當(dāng)app加載自定義組件時,若組件存在工廠函數(shù),app會將自身作為上下文信息以及其后的opts作為參數(shù)傳遞給工廠函數(shù),并使用工廠函數(shù)的返回值作為component組件對象。
// 對外導(dǎo)出工廠函數(shù)
module.exports = function(app, opts) {
return new Component(app, opts)
}
代碼中組件是一個簡單的類,實現(xiàn)了必須的生命周期接口,應(yīng)用需要觸發(fā)生命周期每個階段每個組件所需的回調(diào)函數(shù)。
注冊組件
只有組件實例被注冊進(jìn)入進(jìn)程上下文,應(yīng)用才能獲得該組件實例提供的能力。
使用app.load()將自定義組件注冊到進(jìn)程上下文
-
app.load()用于加載組件,返回調(diào)用鏈app應(yīng)用實例。
app.load([name], comp, [opts]);
| 參數(shù) | 描述 |
|---|---|
| name | 組件名稱,可選。 |
| comp | 組件實例或組件工廠函數(shù) |
| opts | 將被傳入到組件工廠函數(shù)的第2個參數(shù),可選項。 |
- 具名組件實例載入后可通過
app.components.name進(jìn)行訪問 - 若
comp是一個函數(shù),那么app將會把它作為一個工廠函數(shù)并讓其返回一個組件實例。 - 工廠函數(shù)接受兩個參數(shù)
app和opts返回一個組件實例。
app.load()底層函數(shù)聲明
Application.load = function(name, component, opts)
| 參數(shù) | 必填 | 描述 |
|---|---|---|
| name | 可選 | 組件名稱 |
| component | 必填 | 組件工廠函數(shù)創(chuàng)建的實例 |
| opts | 可選 | 組件工廠函數(shù)構(gòu)造器參數(shù) |
組件交互
組件實例可以通過應(yīng)用和其它組件進(jìn)行交互合作。
比如:一個連接組件接收到一個客戶端請求,然后將其發(fā)送給應(yīng)用,處理機組件稍后就有可能從應(yīng)用中獲取這條消息。
基于組件的系統(tǒng)應(yīng)用實際上是進(jìn)程的骨干,它載入了所有的注冊組件,鞭策它們穿越了整個生命周期。單應(yīng)用不能涉及到各組件的細(xì)節(jié)。所有定制服務(wù)端進(jìn)程的作業(yè)僅僅只是挑選必須的組件構(gòu)成一個應(yīng)用。所以應(yīng)用是非常干凈和靈活的,而組件的可重用性非常高。此外,組件系統(tǒng)最終將所有服務(wù)端類型裝入一個統(tǒng)一的進(jìn)程中。
命名規(guī)則
每個組件的名字都在自己的name屬性中,通常為js文件名前后加雙下劃線。
例如:connector.js的組件名稱為__connector__
var pro = Component.prototype;
pro.name = '__connector__';
組件獲取
內(nèi)置組件位于components文件夾下,組件可通過Pomelo.components或Pomelo按名或取,也可以通過Application.components來按名獲取。
加載流程
Pomelo框架的核心是兩個類Pomelo和Application, Application實例由Pomelo.createApp()創(chuàng)建,Pomelo實際上由一系列組件以及一個全局上下文Application實例組成。
在類圖上所有組件都是抽象類Component的子類。每個組件都完成其相應(yīng)的功能,不同的服務(wù)器將會加載不同的組件。
Pomelo應(yīng)用程序執(zhí)行的過程是對相應(yīng)組件的生命周期的管理,實際的邏輯均由組件提供。
$ vim pomelo/lib/pomelo.js
$ vim game-server/app.js
- 導(dǎo)出pomelo對象
const pomelo = require('pomelo');
- pomelo.js文件的作用是初始化所有配置信息并自動加載所有組件
- pomelo中的各個功能模塊都是以
component組件的形式進(jìn)行封裝
/**
* 自定加載已綁定的組件
*/
fs.readdirSync(__dirname + '/components').forEach(function (filename) {
//文件類型驗證
if (!/\.js$/.test(filename)) {
return;
}
//加載文件
var name = path.basename(filename, '.js');
var _load = load.bind(null, './components/', name);
//將文件名作為name,將加載得到的function函數(shù)作為value,保存到pomelo對象中。
Pomelo.components.__defineGetter__(name, _load);
Pomelo.__defineGetter__(name, _load);
});
//加載handler
fs.readdirSync(__dirname + '/filters/handler').forEach(function (filename) {
if (!/\.js$/.test(filename)) {
return;
}
var name = path.basename(filename, '.js');
var _load = load.bind(null, './filters/handler/', name);
Pomelo.filters.__defineGetter__(name, _load);
Pomelo.__defineGetter__(name, _load);
});
//加載rpc
fs.readdirSync(__dirname + '/filters/rpc').forEach(function (filename) {
if (!/\.js$/.test(filename)) {
return;
}
var name = path.basename(filename, '.js');
var _load = load.bind(null, './filters/rpc/', name);
Pomelo.rpcFilters.__defineGetter__(name, _load);
});
function load(path, name) {
if (name) {
return require(path + name);
}
return require(path);
}
當(dāng)使用require("pomelo")導(dǎo)出pomelo對象時,會發(fā)現(xiàn)在pomelo中會直接使用fs.readdirSync讀取文件載入的過程。
由于是執(zhí)行函數(shù)所以在require時會直接執(zhí)行,此時會載入node_modules/pomelo/lib下的components、filters/handler、fitler/rpc文件夾下的所有js模塊,并寫入到pomelo。
其中pomelo.componenets是pomelo平臺的所有組件,對于每個組件而言都有一個application應(yīng)用實例,每個應(yīng)用實例都會通過pomelo對象加載對應(yīng)的components組件并實例化。
Pomelo.__defineGetter__(name, _load)
用于將所有組件以文件名作為name,文件加載得到的function函數(shù)作為value,保存到pomelo對象中。
- Application應(yīng)用初始化流程
//創(chuàng)建應(yīng)用 為客戶端初始化應(yīng)用
const app = pomelo.createApp();
當(dāng)使用pomelo.createApp創(chuàng)建出Application應(yīng)用對象app并初始化后,經(jīng)過一系列app.set和app.configure參數(shù)配置后app.start()就開啟了項目。
$ pomelo/lib/pomelo.js
/**
* 創(chuàng)建一個pomelo應(yīng)用實例
*
* @return {Application}
* @memberOf Pomelo
* @api public
*/
Pomelo.createApp = function (opts) {
var app = application;
//應(yīng)用初始化
app.init(opts);
self.app = app;
return app;
};
pomelo.createApp會調(diào)用Application.init對應(yīng)用進(jìn)行初始化,初始化過程中會調(diào)用AppUtil.defaultConfiguration來讀入默認(rèn)配置。
例如:從master.json中讀取master服務(wù)器配置(Application.master),從servers.json中讀入服務(wù)器集群各個進(jìn)程的type、host和port配置,這里也可以通過Application.get("__serverMap__")進(jìn)行獲取。
$ vim pomelo/lib/application.js
/**初始化服務(wù)器 設(shè)置默認(rèn)的配置 */
Application.init = function(opts) {
opts = opts || {};
this.loaded = []; // 已加載的組件列表
this.components = {}; // name -> component map
this.settings = {}; // collection keep set/get
var base = opts.base || path.dirname(require.main.filename);
this.set(Constants.RESERVED.BASE, base, true);
this.event = new EventEmitter(); // event object to sub/pub events
// 當(dāng)前服務(wù)器信息
this.serverId = null; // 當(dāng)前服務(wù)器id
this.serverType = null; // 當(dāng)前服務(wù)器類型
this.curServer = null; // 當(dāng)前服務(wù)器信息
this.startTime = null; // 當(dāng)前服務(wù)器開啟事件
// 全局服務(wù)器信息
this.master = null; // 主服務(wù)器信息
this.servers = {}; // 當(dāng)前全局服務(wù)器信息映射集合,格式為id -> info
this.serverTypeMaps = {}; // 當(dāng)前全局服務(wù)器類型映射集合,格式為type -> [info]
this.serverTypes = []; // 當(dāng)前全局服務(wù)器類型列表
this.lifecycleCbs = {}; // 當(dāng)前服務(wù)器自定生命周期回調(diào)函數(shù)
this.clusterSeq = {}; // 集群ID序列
//讀取默認(rèn)配置
appUtil.defaultConfiguration(this);
//設(shè)置當(dāng)前服務(wù)器狀態(tài)
this.state = STATE_INITED;
//日志記錄
logger.info('application inited: %j', this.getServerId());
};
lifecycleCbs生命周期回調(diào)可以讓開發(fā)人員在不同類型的服務(wù)器生命周期中進(jìn)行詳細(xì)操作,生命周期回調(diào)函數(shù)包括beforeStartup、afterStartup、beforeShutdown、afterShutdown。
當(dāng)Application.start時會加載默認(rèn)的兩個組件master和monitor,使用Application.load加載組件時會將組件存儲到app的load和component中,不過需要注意的是這里的組件是組件實例化后的對象。
//啟動應(yīng)用
app.start();
$ vim pomelo/lib/application.js
/**
* 開啟應(yīng)用,將會加載默認(rèn)組件并開啟已加載的組件。
* @param {Function} cb callback function
* @memberOf Application
*/
Application.start = function(cb) {
this.startTime = Date.now();
//當(dāng)前狀態(tài)判斷
if(this.state > STATE_INITED) {
utils.invokeCallback(cb, new Error('application has already start.'));
return;
}
//根據(jù)服務(wù)器類型加載默認(rèn)組件
var self = this;
appUtil.startByType(self, function() {
//加載默認(rèn)的組件:master和monitor
appUtil.loadDefaultComponents(self);
var startUp = function() {
appUtil.optComponents(self.loaded, Constants.RESERVED.START, function(err) {
self.state = STATE_START;
if(err) {
utils.invokeCallback(cb, err);
} else {
logger.info('%j enter after start...', self.getServerId());
self.afterStart(cb);
}
});
};
var beforeFun = self.lifecycleCbs[Constants.LIFECYCLE.BEFORE_STARTUP];
if(!!beforeFun) {
beforeFun.call(null, self, startUp);
} else {
startUp();
}
});
};
start函數(shù)是Application的啟動函數(shù),由pomelo繼承,當(dāng)在app.js中使用app.start()是會被調(diào)用。
- 組件加載運行
-
pomelo遍歷components文件夾中各個js文件,require到pomelo和pomelo.componenets中。 -
Application.start開啟后會先調(diào)用AppUtil.loadDefaultComponents -
loadDefaultComponents中會根據(jù)Application.serverType來Application.load所需的components。 -
Application.load中會將Pomelo的components放到自己的components中 -
Application.start/stop/afterStop等方法會統(tǒng)一地執(zhí)行各components中對應(yīng)的start/stop/afterStart鉤子函數(shù)
內(nèi)建組件

Pomelo內(nèi)建組件適用于不同的服務(wù)器,主要包括:master組件、monitor組件、connector組件、session組件、connection組件、server組件、pushScheduler組件、proxy組件、remote組件、dictionary組件、protobuf組件、channel組件、backendSession組件。

不同類型的服務(wù)器啟動的組件
| 服務(wù)器 | 組件 | 描述 |
|---|---|---|
| 所有服務(wù)器 | monitor | - |
| 主服務(wù)器 | - | 啟動所有服務(wù)器及相應(yīng)的監(jiān)控或統(tǒng)計等服務(wù) |
| 后端服務(wù)器 | server | 服務(wù)器對外服務(wù)接口,用于路由解釋、轉(zhuǎn)發(fā)、請求處理等。 |
| 后端服務(wù)器 | proxy | RPC客戶端代理,服務(wù)器賬號策略由app配置,默認(rèn)路由算法。 |
| 后端服務(wù)器 | channel | 為廣播消息服務(wù) |
| 前端服務(wù)器 | connection | 統(tǒng)計使用 |
| 前端服務(wù)器 | connector | 客戶端與服務(wù)器的直接連接 |
| 前端服務(wù)器 | session | 會話管理 |
| RPC服務(wù)器 | remote | RPC服務(wù)器組件 |
| RPC服務(wù)器 | localSession | 由connector發(fā)送消息時copy過來的session數(shù)據(jù) |
組件職責(zé)
| 組件 | 職責(zé) |
|---|---|
| master | 負(fù)責(zé)啟動master服務(wù)器 |
| monitor | 負(fù)責(zé)啟動各個服務(wù)器的monitor服務(wù),該服務(wù)負(fù)責(zé)收集服務(wù)器的信息并定期向master進(jìn)行消息推送,保持master與各個服務(wù)器的心跳連接。 |
| proxy | 負(fù)責(zé)生成服務(wù)器rpc客戶端,由于系統(tǒng)中存在多個服務(wù)器進(jìn)程,不同服務(wù)器進(jìn)程之間相互通信需要通過RPC調(diào)用,master服務(wù)器除外。 |
| remote | 負(fù)責(zé)加載后端服務(wù)器的服務(wù)并生成服務(wù)器RPC服務(wù)端 |
| server | 負(fù)責(zé)啟動所有服務(wù)器的用戶請求處理服務(wù) |
| connector | 負(fù)責(zé)啟動前端服務(wù)器的session服務(wù)和接收用戶請求,可加載connector組件時指定自定義的實現(xiàn),以選擇合適的連接模式或數(shù)據(jù)通信協(xié)議。 |
| sync | 負(fù)責(zé)啟動數(shù)據(jù)同步模塊并對外提供數(shù)據(jù)同步功能 |
| connection | 負(fù)責(zé)啟動用戶連接信息的統(tǒng)計服務(wù) |
| channel | 負(fù)責(zé)啟動channelService服務(wù),channelService服務(wù)提供channel相關(guān)功能,包括創(chuàng)建channel,并通過channel進(jìn)行消息推送等。 |
| session | 負(fù)責(zé)啟動sessionService服務(wù),該服務(wù)主要用來對前端服務(wù)器的用戶session進(jìn)行統(tǒng)一管理。 |
| localSession | 負(fù)責(zé)啟動localSession服務(wù),localSession服務(wù)負(fù)責(zé)維護(hù)服務(wù)器本地session并與前端服務(wù)器進(jìn)行交互。 |
| dictionary | 負(fù)責(zé)生成handler的字典 |
| protobuf | 負(fù)責(zé)解析服務(wù)端和客戶端的protobuffer的定義,從而對客戶端和服務(wù)端的通信內(nèi)容進(jìn)行壓縮。 |
- 服務(wù)器組件是一個功能復(fù)雜的組件,它被除
master以外的服務(wù)器加載 - 服務(wù)器組件會加載并維護(hù)自身的
Filter信息和Handler信息
處理流程
如果客戶端請求的服務(wù)是由前端服務(wù)器提供
- 服務(wù)器組件會從
connector組件的回調(diào)中獲得相應(yīng)的客戶端請求或通知 - 然后使用自己的
before filters對消息進(jìn)行過濾 - 再次調(diào)用自己相應(yīng)的
Handler進(jìn)行請求的邏輯處理 - 將響應(yīng)通過回調(diào)的方式發(fā)送給
connector進(jìn)行處理 - 最后再調(diào)用
after filter進(jìn)行清理處理
如果客戶端請求的服務(wù)是后端服務(wù)器提供的服務(wù)
- 此時會出現(xiàn)
sys rpc調(diào)用 - 前端服務(wù)器自己處理的情況具體調(diào)用更為
doHandler,而發(fā)起rpc調(diào)用的情況則為doForward。
這兩種處理流程不同點在于
- 對于自身的請求,調(diào)用自己的
filter-handler鏈進(jìn)行處理。 - 對于不是前端服務(wù)器自己的服務(wù),則是發(fā)起一個
sys rpc,然后將rpc調(diào)用的結(jié)果作為響應(yīng),發(fā)送給connector進(jìn)行處理。
對于后端服務(wù)器來說其客戶端請求不是直接來源于真實的客戶端,而是來源于前端服務(wù)器對其發(fā)起的sys rpc調(diào)用,這個rpc調(diào)用的實現(xiàn)是pomelo內(nèi)建的msgReote,而msgRemote實現(xiàn)會將來自前端服務(wù)器的sys rpc調(diào)用請求派發(fā)給后端服務(wù)器的server組件,然后后端服務(wù)器會啟用filter-handler鏈對其進(jìn)行處理,最后通過rpc調(diào)用的返回將具體的響應(yīng)返回給前端服務(wù)器。
前端服務(wù)器將客戶端請求向后端服務(wù)器分派時,由于同類型的后端服務(wù)器往往有很多,因此需要一個路由策略,一般情況下用戶通過Application.route調(diào)用為后端服務(wù)器配置的路由。
app.components.proxy
app.components.__proxy__是RPC客戶端組件,源碼位于pomelo/lib/common/components/proxy.js。
- proxy 負(fù)責(zé)生成服務(wù)器rpc客戶端,由于系統(tǒng)中存在多個服務(wù)器進(jìn)程,不同服務(wù)器進(jìn)程之間相互通信需要通過RPC調(diào)用,master服務(wù)器除外。
app.components.__proxy__.client._station