Pomelo

Pomelo(柚子)是基于Node.js的高性能分布式游戏服务框架,它包含基础的开发框架和相关的扩展组件(库和工具包)。Pomelo不但适用于游戏服务器开发也可以用于开发高实时的Web应用,它的分布式架构使Pomelo比普通实时Web框架性能更好。

Pomelo是游戏服务器框架,本质上也是高实时、高扩展、多进程的应用框架,除了在提供的库部分有游戏专用的库,其余部分框架完全可用于开发高实时的应用。而且与现有的Node.js高实时应用框架如Derby、Socketstream、Meteor等相比具有更好的可伸缩性。

Pomelo为什么采用Node.js开发?

Node.js自身特点与游戏服的特性惊人的吻合,在Node.js官方定义中,fast、scalable、realtime、network这几个特性都非常符合游戏服的要求,游戏服是网络密集型的应用,对实时性要求极高,Node.js网络IO上的优势完全可以满足这点。

使用Node.js开发游戏服的优势

  • IO与可伸缩性
    IO密集型的应用采用Node.js最合适,可以达到最好的可伸缩性。
  • 多进程单线程
    Node.js天生采用单线程,使其在处理复杂逻辑时无需考虑线程同步、锁、死锁等问题,减少了很少逻辑错误。由多进程Node.js组成的服务器群是最理想的应用架构。
  • 语言优势
    使用JavaScript开发可以实现快速迭代,若客户端使用HTML5,更可实现代码共用。

游戏服的运行架构

高可扩展的游戏运行架构必须是多进程的,Google的Gritsgame,Mozilla的Browserquest都采用了Node.js作为游戏服开发语言,它们都采用了单进程的Node.js服务器,由于缺乏扩展性,使它们可以支撑的在线用户数量是有限的。而多进程架构可以很好的实现游戏服的可扩展性,达到支撑较多的在线用户、降低服务器压力等要求。

典型多进程MMO运行架构

Pomelo_第1张图片
多进程MMO运行架构
  • 客户端client通过websocket长连接到前端connector服务器群,connector连接器负责承载连接,并将request请求转发到后端服务器群。
  • 后端服务器群包含按场景分区的场景服务器area,聊天服务器chat、状态服务器status,这些服务器负责各自的业务逻辑。
  • 后端服务器负责处理完逻辑后将结果返回给前端连接器,再由连接器广播回给客户端。
  • master负责统一管理这些服务器,包括各服务器的启动、监控、关闭等功能。

架构将游戏服做了抽象,抽象成两类:前端服务器、后端服务器

前端服务器(frontend server)

  • 负责承载客户端请求的连接
  • 维护Session会话信息
  • 将请求转发到后端
  • 将后端需广播的消息发送到前端

后端服务器(backend server)

  • 处理业务逻辑包括RPC和前端请求的逻辑
  • 将消息推送回前端

游戏运行架构 & Web应用运行架构

游戏运行架构 Web运行架构
前端服务器 Web服务器,如Apache/Nginx
后端服务器 应用服务器,如Tomcat

游戏运行架构与Web应用运行架构的区别

  • 长连接与短连接
    Web应用使用基于HTTP的短连接以达到最大的可扩展性,游戏应用采用基于Socket(WebSocket)的长连接以达到最大的实时性。

  • 分区策略
    Web应用的分区可根据负载均衡自行决定,游戏则是基于场景的分区模式,这使同场景的玩家跑在一个进程内以达到最少的跨进程调用。

  • 有无状态
    Web应用是无状态的,可以达到无限的扩展。游戏应用是有状态的,由于基于场景的分区策略,游戏的请求必须路由到指定的服务器,这也使游戏达不到Web应用同样的可扩展性。

  • 通讯方式
    Web应用基于请求响应模式,游戏应用则更为频繁的使用广播,由于玩家在游戏里的行动要求实时地通知场景中的其它玩家,必须通过广播的模式实时发送,这也使游戏在网路通信上的要求高于Web应用。

框架定位

Pomelo是一个轻量级的服务器框架,最合适的应用领域是网页游戏、社交游戏、移动游戏的服务端。不推荐将Pomelo作为大型MMORPG游戏开发,尤其是大型3D游戏,这需要像BigWorld商用引擎来支撑。

框架特性

  • 基于Socket.io开发
  • 多进程架构:支持MMO场景分区和其他各类分区策略
  • 服务器扩展:快速扩展服务器类型和数量。
  • 通讯机制:请求、响应、广播
  • 扩展组件

框架组成

  • Server Management
    多进程分布式游戏服武器,各游戏Server进程的管理是框架很重要的部分,框架公国抽象使服务器的管理非常容易。
  • Network
    请求、响应、广播、RPC、Session管理
  • Application
    应用的定义,组件管理,上下文配置
Pomelo_第2张图片
柚子框架

架构目标

  • 服务器进程的抽象与扩展
    Web应用中每个服务器是无状态且对等的,开发者无需通过框架或容器来管理服务器。但游戏不同,游戏可能需要包含多种不同类型的服务器,每种服务器在数量上也可能有不同的需求。这就需要框架对服务器进行抽象和解耦,支持服务器类型和数量上的扩展。
  • 客户端的请求响应、广播
    客户端的请求响应与Web应用类似,但游戏框架是基于长连接的,实现模式与HTTP请求有一定差异。广播是游戏服务器最频繁的操作,需要方便且高性能的API。
  • 服务器间的通讯和调用
    尽管框架尽量避免跨进程调用,但进程间的通讯是不可避免的,因此需要一个方便好用的RPC框架做支撑。
  • 松耦合可插拔
    框架支持以组件的形式插入任何第三方组件,也支持加入自定义的路由规则,自定义的过滤器等。

框架优势

  • 可伸缩性好
    架构采用多进程单线程的运行架构,扩展服务器方便。
    Node.js的网络IO优势提供了高可伸缩性。
  • 使用方便
    开房模式与Web应用的开发类似,都是基于Convention Over Configuration的理念,零配置。
  • 松耦合可扩展
    遵循Node.js微模块原则,框架所使用的类库组件都是以NPM Module的形式扩展进来。

安装配置

环境准备

  • 操作系统 Windows10
  • Python版本:2.5~3.0
  • Visual Studio2010

全局安装框架

$ npm i -g pomelo

查看帮助

$ pomelo -h
  用法: pomelo [选项] [命令]
  命令:
    init [path]            创建一个新的应用
    start [options]        开启应用
    list [options]         列出所有的服务器
    add [options]          添加一个新的服务器
    stop [options]         关闭服务器,多个服务器可使用 `pomelo stop server-id-1 server-id-2`
    kill [options]         杀死应用
    restart [options]      重启服务器,多个服务器可使用`pomelo restart server-id-1 server-id-2`
    masterha [options]     开启主从模式下所有的从服务器
    *
  选项:
    -h, --help     输出用法信息
    -V, --version  输出版本号

创建项目

$ cd workspace
$ pomelo init ./test
默认管理员用户:
  账户: admin
  密码: admin
可编辑adminUser.json文件配置管理员用户
请选择底层连接器: 1 websocket(原生 socket), 2 socket.io, 3 wss, 4 socket.io(wss), 5 udp, 6 mqtt: [1]

目录结构

目录文件 描述
game-server 游戏服务器,以app.js文件为入口。
shared 前后端、游戏服、Web服共用代码
web-server Web服务器,Express框架搭建,以app.js为入口。

游戏服目录

目录 描述
game-server/app 游戏服应用开发目录,实现不同类型的服务器,添加对应Handler和Remote等。
game-server/config/ 游戏服配置文件目录,配置文件以JSON格式定义。
game-server/logs/ 游戏服日志目录
game-server/app.js 游戏服入口文件

Web服目录

目录文件 描述
web-server/bin/ Web服应用保存目录
web-server/public/ Web服静态文件保存路径
web-server/app.js Web服入口文件

安装项目

$ cd test
$ npm-install.bat

安装脚本执行的步骤

$ cd ./game-server && npm install -d
$ cd ./web-server && npm install -d

启动项目

启动项目必须分别启动游戏服和Web服

  • 启动游戏服命令
$ cd game-server && pomelo start

启动命令

$ pomelo start [development|product] [--daemon]
命令参数 描述
development 默认,开发模式
product 产品环境
--deamon 后台运行

后台运行模式--daemon需forever模块支持

$ npm i -g forever 

项目应用启动命令

$ pomelo start -h
  用法: start [选项]
  选项:
    -h, --help                    输出用法信息
    -e, --env                指定环境
    -D, --daemon                  以后台守护进程模式启动
    -d, --directory,   指定代码目录
    -t, --type ,     指定服务器类型启动
    -i, --id           指定服务器ID启动
  • 启动Web服命令
$ cd web-server && node app

访问Web服:http://127.0.0.1:3001

查看游戏服务器状态

$ cd test
$ pomelo list
try to connect 127.0.0.1:3005
serverId           serverType pid   rss(M) heapTotal(M) heapUsed(M) uptime(m)
connector-server-1 connector  8288  37.84  21.83        17.02       5.48
master-server-1    master     22696 35.04  19.83        14.88       5.48

游戏服务器状态信息

字段 含义
serverId 服务器编号,从config配置表中的ID。
serverType 服务器类型,同config配置表中的type。
pid 服务器对应的进程PID
rss -
heapTotal 服务器使用堆内存总大小,单位MB。
heapUserd 服务器已使用堆内存大小,单位为兆(MB)。
uptime 服务器启动时长,单位为分钟。

关闭项目

$ cd test
$ pomelo stop [id]

pomelo stop优雅地关闭各个服务器

  • 前端服务器首先断开连接,阻止新玩家进入游戏,用户体验好。
  • 各个服务器按顺序关闭自身的功能,保证游戏逻辑正常。
  • 玩家状态等信息及时写入数据库,保证数据完整性。

pomelo stop id会关闭指定ID的服务器,关闭特定服务器会导致服务器状态信息等丢失,建议先做好服务器状态信息的维护和备份。

杀死进程

$ cd test
$ pomelo kill [--force]

pomelo kill会直接杀死项目进程,做法比较粗暴,安全性低,开发环境下可以使用,产品环境慎用。若有残留进程杀不干净,可添加--force参数。

动态添加服务器

$ cd test
$ pomelo add  host=[host] port=[port] id=[id] serverType=[serverType]

目前只支持后端服务器的动态添加

管理控制器

$ apt-get install sysstat

$ git clone https://github.com/NetEase/pomelo-admin-web.git
$ cd pomelo-admin-web
$ npm i -d
$ node app

$ vim config/admin.json
{
    "host": "localhost", 
    "port": 3005,
    "username": "monitor",
    "password": "monitor"
}

Chrome浏览器访问:http://127.0.0.1:7001

术语

鸭子类型

动态语言面向对象有鸭子类型的概念,服务器的抽象也同样可以比喻为鸭子,服务器的对外接口只有两类:

  • handler:接收客户端的请求
  • remote:接收RPC请求

handler和remote的行为决定了服务器长什么样子,只要定义好handler和remote两类的行为,就可以确定这个服务器的类型。

网关服务器 gate

  • 一个应用的gate一般不参与RPC调用,也就是说其配置项里可以没有port字段,仅仅有clientPort字段,它的作用是做前端的负载均衡。
  • 客户端往往首先向gate发出请求,网关会给客户端分配具体的connector。具体的分配策略一般是根据客户端的某个key做hash得到connector的ID,这样就可以实现各个connector的负载均衡。

连接服务器 connector

  • connector接收客户端的连接请求,创建与客户端的连接,维护客户端的session会话信息。
  • connector接收客户端对后端服务器的请求,按照用户配置的路由策略,将请求路由给具体的后端服务器。当后端服务器处理完请求或需要给客户端推送消息的时候,connector同样会扮演一个中间角色,完成对客户端的消息发送。
  • connector会同时拥有clientPort和port,其中clientPort是用来监听客户端的连接,port端口用来给后端提供服务。

应用逻辑服务器

  • gate和connector又称为前端服务器,应用逻辑服务器是后端服务器,它完成实际的应用逻辑,并提供服务给客户端,当然客户端的请求是通过前端服务器路由过来的。
  • 后端服务器之间也会通过RPC调用而有相互之间的交互,由于后端服务器不会跟客户端直接有连接,因此后端服务器只需监听它提供服务的端口即可。

主服务器 master

master加载配置文件,通过读取配置文件,启动所配置的服务器集群,并对所有服务器进行管理。

RPC调用 rpc

Pomelo中使用RPC调用进行进程间通信,在Pomelo中RPC调用分为两类:使用namespace命令空间进行区分,namespacesys的是系统RPC调用,它对用户来说是透明的,目前Pomelo中系统RPC调用有:

  • 后端服务器向前端服务器请求session会话信息
  • 后端服务器通过channel推送消息时对前端服务器的RPC调用
  • 前端服务器将用户请求路由给后端服务器时也是sys rpc调用

除了系统RPC调用之外,其余的由用户自定义的RPC调用属于user namespace的RPC调用,需要用户自己完成RPC服务端remotehandler代码,并由RPC客户端显式地发起调用。

路由 router

route是用来标识一个具体服务或客户端接受服务端推送消息的位置,对服务器来说,其形式一般是:chat.chatHandler.send,其中chat是服务器类型,chatHandler是chat服务器中定义的一个Handler,send则是这个Handler中的一个handler方法。

对客户端来说,其路由形式一般为onXXX,当服务端请求到达后,前端服务器会将用户客户端请求派发到后端服务器,这种派发需要一个路由函数router,可以粗略地认为router是根据用户的session以及其请求内容做一些运算后,将其映射到一个具体的应用服务器ID。

可以通过application的route调用给某一类型的服务器配置其router。若不配置的话,Pomelo会使用一个默认的router。

Pomelo默认的路由函数是使用session里面的uid字段,计算uid字段的crc32校验码,然后用这个校验码作为key,跟同类应用服务器数目取余,得到要路由到的服务器编号。注意这里有一个陷进,如果session没有绑定uid的话,此时uid字段为undefined,可能会造成所有请求都路由到同一台服务器,所以在实际开发中还是需要自己来配置router。

会话 session

Session会话是指一个客户端连接的抽象,Pomelo框架中有三个session会话的概念分别是Session、FrontendSession、BackendSession。

session会话字段结构

{
  id://readonly
  frontendId:  //readonly
  uid:  // readonly
  settings:  //read and write
  __socket__: 
  __state__: 
  //...
}
字段 权限 描述
id 只读 当前session会话的ID,全局唯一,自增方式来生成。
frontendId 只读 维护当前session会话的前端服务器的ID
uid 只读 当前session会话所绑定的用户ID
settings 读写 维护key-value map用来描述session会话的自定义属性
__socket__ 只读 底层原生socket的引用
__state__ 只读 用来指明当前session会话的生命周期状态

一个session会话一旦建立,那么idfrontendIduid__socket____state__都是确定的,都应该是只读不可写的。而settings也不应该被随意修改。因此在前端服务器中引入了FrontendSession可将其看作是一个内部session会话在前端服务器中的傀儡。

FrontendSession的字段结构

{
  id:  // readonly
  frontendId:  // readonly
  uid:  // readonly
  settings:  // read and write
}

FrontendSession的作用

  • 通过FrontendSession可以对settings字段进行设置值,然后通过调用FrontendSession的push()方法,将设置的settings的值同步到原始的session会话中。
  • 通过FrontendSession的bind调用可以给session绑定uid
  • 通过FrontendSession访问session的只读字段,不过对FrontendSession中与session中相同的只读字段的修改并不会反映到原始的session中。

服务 service

Pomelo框架有两个service服务:SessionService、BackendSessionService

SessionService维护所有的原始session信息,包括不可访问的字段,绑定的uid以及用户自定义的字段。

BackendSession与FrontendSession类似,BackendSession是用于后端服务器的,可以看作是原始session的代理,其数据字段跟FrontendSession基本一致。

BackendSession是由BackendSessionService创建并维护的,在后端服务器接收到请求后,由BackendSessionService根据前端服务器RPC的参数进行创建。

对BackendSessionService的每次方法调用实际上都会生成一个远程过程调用,比如通过一个sid获取其BackendSession。同样对于BackendSession中字段的修改也不会反映到原始的session中,不过与FrontendSession一样,BackendSession也有push、bind、unbind调用,它们的作用与FrontendSession一样都是用来修改原始中的settings字段或绑定/解绑uid的,不同的是BackendSession的这些调用实际上都是namespace为sys的远程调用。

频道 Channel

channel可以看作是玩家ID的容器,主要用于需要广播推送消息的场景。

可以把玩家加入到一个channel中,当对这个channel推送消息时,所有加入到这个channel的玩家都会受到推送过来的消息。

一个玩家的ID可能会加入多个channel中,这样玩家就会受到其加入的channel推送过来的消息。

需要注意的时channel都时服务器本地的,应用服务器A和B并不会共享channel,也就时说在服务器A上创建的channel只能 由服务器A才能给它推送消息。

消息类型 request response notify push

Pomelo中有四种消息类型的消息分别是requestresponsenotifypush

  • 客户端发起request请求到服务器,服务器处理后返回response响应。
  • notify是客户端发给服务器的通知,也就是不需要服务器给与回复的请求。
  • push是服务器主动给客户端推送消息的类型

过滤器 filter

filter分为beforeafter两类,每个filter都可以注册多个形成一个filter链,所有客户端请求都会经过filter链进行处理。

  • before filter会对请求做一些前置处理,如检查当前玩家是否已经登录,打印统计日志等。
  • after filter是进行请求后置处理的地方,比如释放请求上下文的资源,记录请求总耗时等。
  • after filter中不应该再出现修改响应内容的代码,因为在进入after filter前响应就已经被发送给客户端。

处理器 handler

handler是实现具体业务逻辑的地方,在请求处理流程中,handler位于before filterafter filter之间。

handler的接口声明:

handler.methodName = function(msg, session, next){
  //...
}

after filter的参数含义和before filter类似,handler处理完毕后,如果需要返回给客户端响应,可以将返回结果封装成JavaScript对象,通过next传递给后续流程。

你可能感兴趣的:(Pomelo)