Pomelo的App Server规范中指出servers包下每个服务器子包下的每个文件均为一个module模块,module中export方法可被分派请求的method方法。
例如:聊天服务下包含三个文件夹分别是filter过滤器、handler客户端请求处理器、remote远程调用其它内部服务器请求处理器。
文件夹 | 描述 |
---|---|
filter | 当前服务器各个module.method执行各个阶段的filter过滤器,分为before和after等。 |
handler | 当前服务器处理客户端请求,客户端通过connector server的socket通道发送过来的request请求,请求的路由格式为serverType.module.method。 |
remote | 非客户端直接请求,由服务器发出的RPC调用处理逻辑。 |
Pomelo是微内核+插件的实现方式,由component组件构成,每个component组件完成特定的任务。
Pomelo中组件是可重用的服务单位,一个组件实例提供若干种服务,比如说处理机组件载入后处理机代码后将会把客户端消息传递给请求处理机。
一个组件实例要被注册进入进程上下文(应用),这样后者就会获得该实例提供的能力。组件实例可以通过应用和其它组件进行交互合作。比如一个连接组件接收到一个客户端请求,然后将其发送给应用,那么一个处理机组件等一下就有可能从应用中获取这条消息。
而在代码中,组件是一个非常简单的类,实现了一些必须的生命周期接口,应用需要触发生命周期每个阶段每个组件所需的回调函数。
组件系统
组件的生命周期
pomelo框架是由一些松耦合的component组成,每个component完成一些功能。整个pomelo框架可以看作是一个component容器,完成component的加载以及生命周期管理。pomelo的核心功能都是由component完成的,每个component往往有start、afterStart、stop等调用,用来完成生命周期管理。
- start(cb)
服务端在启动阶段被调用的开始生命周期,注意各个组件需要调用cb回调函数来持续后续步骤,组件也可以传入一个参数给cb来表示但钱组件启动失败,以此来使应用结束这个进程。 - afterStart(cb)
服务器在启动之后的声明周期阶段回调,需要在当前进程所有被注册组件启动之后调用,它给这些组件进行一些协作兼初始化的机会。 - stop(cb)
服务器在停止生命周期阶段的回调,当服务器将要停止的时候调用。组件可以做些清理作业,比如冲刷此周期中的数据到数据库。force参数若为true则表示所有组件都需要被立即停止。
自定义组件
$ vim game-server/app/components/defcomp.js
const DEFAULT_INTERVAL = 3000;//间隔毫秒
/*自定义组件*/
const DefComp = function(app, opts){
this.app = app;
this.interval = opts.interval || DEFAULT_INTERVAL;//间隔毫秒
this.timerId = null;//定时器
};
DefComp.name = "__demo__";
/**hook钩子函数,供pomelo管理其生命周期时进行调用。*/
DefComp.prototype.start = function(cb){
console.log("DefCom Start");
const self = this;
this.timerId = setInterval(function(){
console.log("server id: ", self.app.getServerId());
}, this.interval);
process.nextTick(cb);
};
DefComp.prototype.afterStart = function(cb){
console.log("DefCom afterStart");
process.nextTick();
};
DefComp.prototype.stop = function(force, cb){
console.log("DefCom stop");
clearInterval(this.timerId);
process.nextTick(cb);
};
module.exports = function(app, opts){
return new DefComp(app, opts);
};
每个component都需要定义start、afterStart、stop这些hook函数,供pomelo管理其生命周期时进行调用。对于component的启动,pomelo总是先调用其加载的每个component提供的start()函数。当全部调用完毕后,才会去调用其加载每个component的afterStart()函数,这里总是按顺序调用的。因为调用afterStart()函数的时候,所有的component的start()函数已经调用完毕,可以添加一些需要全局就绪的工作。stop()函数用于程序结束时对component进行清理时使用。
注册组件
app.load([name], comp, [opts]);
参数 | 描述 |
---|---|
name | 可选的组件名称,命名的组件实例可以被载入后通过app.components.name进行访问。 |
comp | 组件实例或组件工厂函数,若comp是一个函数,那么应用将会把它作为一个工厂函数并让其返回一个组件实例。工厂函数具有两个参数app和opts,并且它将返回一个组件实例。 |
opts | 可选项,将被传入至组件工厂函数的第二个参数。 |
自定义组件配置使用
$ vim game-server/app.js
const defcomp = require("./app/components/comdef");
app.configure("production|development", "master", function(){
app.load(defcomp, {interval:5000});
});
基于组件系统应用实际上是进程的骨干,它载入了所有的注册组件,鞭策它们穿越了整个生命周期。单应用不能涉及到各组件的细节。所有定制服务端进程的作业仅仅只是挑选必须的组件构成一个应用。所以应用是非常干净和灵活的,而组件的可重用性非常高。此外,组件系统最终将所有服务端类型装入一个统一的进程中。
Pomelo组件加载流程
Pomelo框架的核心是两个类Pomelo和Application, Application实例由Pomelo创建,Pomelo实际上由一系列的组件以及一个全局的上下文Application组成。在类图上所有的组件都是以Co
进行命名,都是抽象类Component
的子类。每个组件都完成其相应的功能,不同的服务器将会加载不同的组件。Pomelo应用程序执行的过程是对相应组件的生命周期的管理,实际的逻辑均由Pomelo组件提供。
导出pomelo对象
当使用require("pomelo")
导出pomelo对象时,会发现在pomelo中会直接使用fs.readdirSync读取文件载入的过程。由于是执行函数所以在require时会直接执行,此时会载入node_modules/pomelo/lib下的components、filters/handler、fitler/rpc文件夹下的所有js模块,并写入到pomelo。其中pomelo.componenets是pomelo平台的所有组件,对于每个组件而言都有一个application应用实例,每个应用实例都会通过pomelo对象加载对应的components组件并实例化。
应用初始化流程
当使用Pomelo.createApp创建出Application对象并初始化后,经过一系列的Application.set和Application.configure参数配置后Application.start()就开启了项目。在Pomelo.createApp是会调用Application.init对应用进行初始化,初始化过程中会调用AppUtil.defaultConfiguration来读入默认配置。例如从master.json中读取master服务器配置(Application.master),从servers.json中读入服务器集群各个进程的type、host和port配置,这里也可以通过Application.get("_serverMap_")进行获取。
当Application.start时会加载默认的两个组件master和monitor,使用Application.load加载组件时会将组件存储到app的load和component中,不过需要注意的是这里的组件是组件实例化后的对象。
组件命名规则
系统内置组件位于components文件夹下,各个组件可以通过Pomelo.components或直接使用Pomelo按名取,也可以通过Application.components来按名获取,每个组件的名字都在自己的name属性中,通常为js文件名前后加双下划线,例如connector.js的组件名称为_connector_。
组件加载运行
Pomelo会遍历components文件夹中各个js文件,然后require到Pomelo和Pomelo.componenets中。Application.start开启后会先调用AppUtil.loadDefaultComponents,loadDefaultComponents中会根据Application.serverType来Application.load所需的components。Application.load中会将Pomelo的components放到自己的components中,Application.start/stop/afterStop等方法会统一地执行各components中对应的start/stop/afterStart方法。
Pomelo内建组件
Pomelo内建组件适用于不同的服务器,主要包括:master组件、monitor组件、connector组件、session组件、connection组件、server组件、pushScheduler组件、proxy组件、remote组件、dictionary组件、protobuf组件、channel组件、backendSession组件。
不同类型的服务器启动的组件
服务器 | 组件 | 描述 |
---|---|---|
所有服务器 | monitor | - |
主服务器 | - | 启动所有服务器及相应的监控或统计等服务 |
后端服务器 | server | 服务器对外服务接口,用于路由解释、转发、请求处理等。 |
- | proxy | RPC客户端代理,服务器账号策略由app配置,默认路由算法 |
- | channel | 为广播消息服务 |
前端服务器 | ||
- | connection | 统计使用 |
- | connector | 客户端与服务器的直接连接,可加载connector组件时指定自定义的实现,以选择合适的连接模式或数据通信协议。 |
- | session | 会话管理 |
RPC服务器 | ||
- | remote | RPC服务器组件 |
- | localSession | 由connector发送消息时copy过来的session数据 |
Pomelo组件职责
- master组件
负责启动master服务器 - monitor组件
负责启动各个服务器的monitor服务,该服务负责收集服务器的信息并定期向master进行消息推送,保持master与各个服务器的心跳连接。 - proxy组件
负责生成服务器rpc客户端,由于系统中存在多个服务器进程,不同服务器进程之间相互通信需要通过RPC调用,master服务器除外。 - remote组件
主要负责加载后端服务器的服务并生成服务器RPC服务端 - server组件
负责启动所有服务器的用户请求处理服务 - connector组件
负责启动前端服务器的session服务和接收用户请求 - sync组件
负责启动数据同步模块并对外提供数据同步功能 - connection组件
负责启动用户连接信息的统计服务 - channel组件
负责启动channelService服务,channelService服务提供channel相关功能,包括创建channel,并通过channel进行消息推送等。 - session组件
负责启动sessionService服务,该服务主要用来对前端服务器的用户session进行统一管理。 - localSession组件
负责启动localSession服务,localSession服务负责维护服务器本地session并与前端服务器进行交互。 - dictionary组件
负责生成handler的字典 - protobuf组件
负责解析服务端和客户端的protobuffer的定义,从而对客户端和服务端的通信内容进行压缩。