游戏服务器不同于Web服务器,其在逻辑复杂度、消息量、实时性等方面有更高的要求。本文参考官方文档的Overview部分,进行简单的汇总整理,对Pomelo的设计动机、Pomelo 框架及相关工具和库等进行介绍。
1. 设计动机
最初,Pomelo 被设计为一个游戏服务器,但在设计和开发完成后,发现其可做为一个通用的分布式、实时应用程序开发框架。在此,通过分析游戏服务器需求来说明一下Pomelo的设计动机。
1.1 什么是游戏服务器
没有做过游戏服务器开发的人,可能会觉得游戏服务器很神秘。但实际上,它并不比Web服务器复杂,它只是为客户端的网络请求提供服务。本质上它只是基于长连接的socket服务器。相比Web服务器来说,游戏服务器在逻辑复杂度、消息量、实时性等方面要求更高。
以下是游戏服务器和Web服务器之间的一些差异:
复杂的Socket服务器
我们可以将Web服务器做为一个HTTP服务器看待,而将游戏服务器做为一个原始的Socket服务器。其通过Socket通讯来处理服务器和客户端之间的交互,因此许多游戏服务器直接基于原生Socket(即TCP)来实现。
相比简单的Socket服务器,游戏服务器的任务更为繁重,主要体现在以下几个方面:
长连接及时实性
Web服务器使用基于连接的响应/请求模式,而游戏服务器使用长连接,因此Web服务器所持有和需要的资源要远远少于游戏服务器。基于HTTP使用短连接可以大大Web服务器的伸缩性。
Web服务器可以使用短连接是因为:
而游戏服务器只能使用长连接,其原因如下:
分区策略和负载均衡
一般来说,Web应用之间没有交互的概念,所有用户之间的交互是平等的。在Web应用中,交互频率与用户的地理位置无关,而在游戏应用中则相反。游戏服务器中,玩家的义互频率与玩家的位置(区域)密切相关。如,两个相邻的玩家会互相攻击或者组队来攻击怪物,他们之间的交互会非常频繁且实时性要求很高。这也意味着,两个玩家需要被分配到同一区域服务器进程中,以减少跨进程成本。
因此,游戏应用应该根据区域的不同,而有一个分区策略。这也是与Web应用的不同之处,如下所示:
服务器进程可能位于一个区域或多个区域,因此游戏服务器的可伸缩性会受区域的限制。如果一个区域太忙,超出了它的容量,那么整个游戏服务器就会被阻塞或关闭。区域服务器是有状态的,来自特定玩家的请求必须发送到同一区域服务器。
有状态的服务器会给我们带来了很多问题,会导致该区域服务器在可扩展性和可用性方面不如Web服务器。通常,我们必须通过隔离游戏服务器来缓解这些问题。
Web应用可以以常规方式来划分负载均衡,而游戏应用会基于区域策略进行划分程序开发,使同一区域内的玩家能够在同一区域的服务器进程中运行,以减少跨进程成本。
可扩展性与分布式
无论是Web应用程序还是游戏应用程序,可伸缩性都是最重要评估指标之一。同时这也是要解决的难题之一,会涉及到运行架构及各种优化策略。可以通过一个可扩展设计,以保证在线玩家数及响应时间。在传统游戏服务器的架构中,会使用一个单进程模型来处理所有逻辑,在这种架构下,线上没有大量玩家时可以使用。但随着玩家数量的增加,会带来巨大很大问题。因此,分布式、多进程的游戏服务器架构就成为必然的选择。
以下说明了Web服务器和游戏服务构架的不同之处:
可以看出,Web服务器可以通过单个负载均衡器将请求重定向到任何进程,因此它的运行结构相对简单,也很少需要分布式。而游戏服务器使用了蜘蛛式网络架构,每个进程都有自己的职责,这些进程又相互交织在一起完成一项任务。因此,游戏服务器是一种典型的分布式体系结构。
1.2 难点
通过以上分析我们可以知道,游戏服务器使用了蜘蛛式网络架构,这也给游戏开发带来了一些困难。包括:
实时性保证
对于一个游戏服务器来说,会包含一些实时性任务。包括:
实时tick
通常,游戏服务器需要定时tick来执行定时任务。为了实现实时的行为,这tick定时器会在100ms。这些任务一般包含以下逻辑:
由于定时器间隔在100ms以内,所以,上面任务的处理时间也应该控制在100ms以内。
广播
当一个玩家做某一动作后,必须实时通知同一区域内的其它玩家。这时就需要广播,这也使得游戏应用的网络需求远远高于Web应用。
广播在游戏中的开销很大。由于玩家间输入/输出信息的不对称,如:玩家只是稍微移动,服务器就要把这个动作传递给所有其他玩家,以使他们可以在同一个区域看到这个玩家。当一个区域内的玩家数量较少,广播消息数量不多,但如果玩家数量达到较高水平,广播消息数量将成倍增长。如下所示:
如上js游戏开发框架,当某一区域中有1000个玩家,每个玩家做一个动作,服务器需要通知区域内的所有玩家,这时广播的消息将达到10000000个,足以阻塞服务器而放弃其它任何操作。
分布式
我们可能会在很多地方看到这一观点:分布式开发是困难的。主要体现在以下几点:
多进程(服务)管理
游戏服务器通常采用多进程模型。这些进程间又会有相互,因此这些进程的管理非常困难。
如果没有对服务器(进程)的统一抽象和管理,在开发环境中启动这些服务器会非常复杂,也会很影响开发效率。更重要的是,重量级进程消耗了大量的机器资源,一般用于开发的服务器无法承受这么多处理,而多服务器又会带来进程间调试的困难。
RPC调用
RPC调用方案已经出来很多年了js游戏开发框架,但开发效率仍没有显著提高。
以下我们通过一个流行的RPC框架thrift,来演示RCP调用流程。在这个流程中会包含以下步骤:
而当所定义的接口发生改变时,我们就需要重复以上过程。在不稳定的开发环境中,这种RPC调用方式会严重影响开发效率,因此我们需要一种更灵活的方式。
分布式事务&异步操作
尽管我们试图把相关逻辑放到一个进程中,分布式事务仍然是不可避免的。在使用普通编程语言时,分布式异步提交操作不是一件容易的事。
负载均衡&高可用
由于游戏服务器是有状态的,所以特定玩家的请求需要通过路由规则路由到同一服务器。我们可以很轻松的将请求路由负载到无状态Web服务器,而对于有状态的服务器,使其高可用是非常困难的。但也有方法可以做到这一点,这里介绍以下两种方法:
在Pomelo V0.5中提供了高可用机制,通过zookeeper和redis可以解决一些服务器(如:主服务器)高可用问题,但在实际复杂应用中还需要由应用自己来处理。
原生Socket开发中的问题
我们可以基于原生Socket进行开发材质材料,但使用原生Socket也会带来一些问题:
1.3 基于框架的解决方案
因此,我们需要一个游戏开发框架。除了游戏逻辑外,其它工作都可以由框架完成,服务器的抽象性、可伸缩性、可扩展性都可以由框架解决,以避免重复开发。
Pomelo 是基于Node.js的使用MIT开源许可证的开源框架,它旨在提供一个高性能、可伸缩、轻量级的游戏服务器框架。与其他类似的框架相比,它的主要有以下优点:
2. Pomelo 框架概览
一个可扩展的游戏服务器的运行时架构必须是多进程的,因为单进程扩展受限。谷歌的gritsgame和Mozilla的Browserquest都是使用Node.js作为游戏服务器平台,但他们都是一个单进程模型,这意味着他们的在线用户数是有限的,且缺乏扩展性。
相比来说,Pomelo具有明显的优势,其特点如下。
2.1 典型的多进程架构
如下所示,是一个典型的多进程MMO游戏服务器的运行时架构:
在以上架构中:
2.2 Pomelo 框架介绍
Pomelo 框架中的组件
Pomelo 框架中包含以下组件:
2.3 设计目标
服务器抽象
服务器类型
Pomelo中有两类服务器:前端服务器、后端服务器。如下所示:
其中,前端服务器负责:
而后端服务器负责:
鸭式服务器
“鸭式”是面向对象(OOP)动态编程语言中的一个常用概念,这一概念也可以被用于服务器抽象。我们只需要定义两种服务器接口:一种是处理来自客户端的请求,被称为Hanldler;另一个处理RPC调用,称为Remote。
这样,只要我们为服务器定义了remote和handler,就可以确定服务器的类型及其功能。
服务器抽象的实现
实现服务器的最简单的方法是使服务器代码的组织结构对应相应的目录。如下所示:
如,我们可以定义一个名为"area"的服务器,服务器的具体行为由"handler"和"remote"中的代码决定。对于开发人员来说,只要和handler和remote中编写对应的代码即可。
为了让服务器运行起来,需要在servers.json文件中进行一些简单的配置:
{ "development": { "connector": [ {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientPort": 3010, "frontend": true}, {"id": "connector-server-2", "host": "127.0.0.1", "port": 3151, "clientPort": 3011, "frontend": true} ] , "area": [ {"id": "area-server-1", "host": "127.0.0.1", "port": 3250, "area": 1}, {"id": "area-server-2", "host": "127.0.0.1", "port": 3251, "area": 2}, {"id": "area-server-3", "host": "127.0.0.1", "port": 3252, "area": 3} ] , "chat": [ {"id": "chat-server-1", "host": "127.0.0.1", "port": 3450} ] } }
请求/响应、广播
虽然在游戏中我们会使用长连接,但是请求/响应的API也类似于Web。如:
Pomelo中请求/响应API很像“Ajax”,但它使用是长连接。在如上所示的请求路由"chat.chatHandler.send"中,其中chat表示服务器类型、chatHandler表示处理器(handler)、而send表示具体的请求处理方法。基于“约定优于配置”的原则,这里不需要任何配置,即可完成客户端请求的处理及响应。
除此之外,Pomelo还提供了过滤器(filter)、广播/多播机制、及频道(channel)支持等。
RPC调用抽象
Pomelo 提供了非常简单的rpc框架。它能够根据路由规则路自动选择并调用目标服务器,且不需要任何配置。示例如下:
如,在chatRemote.js文件中,有一个如下的接口定义:
chatRemote.kick = function (uid, player, cb) { }
我们可以RPC客户端通过如下方式调用:
app.rpc.chat.chatRemote.kick (session, uid, player, function (data) { }) ;
注意,在在请求参数中session用于路由请求,框架将基于路由规则自动确定所要调用的服务器。
可插拔组件
在Pomelo中组件是可插拔的模块。开发者可以定义自己的组件,并将其插入到Pomelo中。Pomelo的核心功能全部由内置组件实现,换句话说,Pomelo框架只是它的组件的容器。
组件的生命周期类似如下:
开发者自定义的组件中应实现:start、afterStart、stop等接口,然后就可以app.js文件中像下面这样加载组件:
app.load([name], comp, [opts])
除组件外,Pomelo 还支持插件,插件同样是基于组件实现,可以认为是组件的集合。插件是一个独立的npm模块,使用插件,开发者可以很容易地扩展框架,而不会对框架核心功能产生任何影响。
Pomelo 提供了一些内置插件如下:
3. Pomelo 工具与库
Pomelo 提供了一系列开发工具和库,以帮助开发者开发、调试、部暑。这些工具和库的提供了许多功能,包括服务器管理控制、压力测试及一些常用功能库。
3.1 Pomelo Command-Line Tool
该工具可以帮助开发人员更方便、高效地开发应用程序,其功能包括创建项目、启动应用、停止应用、关闭应用等。详见:
3.2 Pomelo-cli
Pomelo-cli是一个用于管理服务器集群的命令行客户端,它应该首先连接并注册到主服务器,然后就可以通过它向服务器集群发送一些管理命令,如:动态启动服务器、查看服务器状态等。详见:
3.3 Pomelo-robot
Pomelo-robot是一个用于运行基准测试和压力测试的框架。详见:
3.4 Pomelo-daemon
Pomelo-daemon提供了守护进程服务,可以使用此服务进行分布式部署和日志收集。详见:
3.5 Pomelo-admin-web
Pomelo-admin-web是一个基于Pomelo-admin实现的服务器集群监控Web客户端。可以使用它通过浏览器监控服务器集群的运行状态、性能、日志和其他信息。详见:
3.6 Pomelo-sync
Pomelo-sync是一个用于管理数据同步的模块,它可以用于内存和外部存储系统之间的数据同步。详见:
3.7 Pomelo-protobuf
Pomelo-protobuf是一个对google protobuf的变体实现,该模块通过一个json文件代替了原本的.proto文件。它可以动态的解析json文件,从而避免编译.proto文件。在Pomelo框架中,它用于通讯消息的压缩。详见:
4. 客户端平台支持
客户端和服务器之间的协议是开放、可定制的,理论上讲,你可以在任何协议、任何平台上连接到Pomelo游戏服务器。
为方便开发者,Pomelo官方还提供了一些常用平台的客户端SDK,包括:Web、iOS java & android、unity3d、flash、及C语言库等。
以下是SDK列表:
C iOS Android & Java Unity3d Cocos2dx Flash
文章来源:https://blog.csdn.net/awhlmcyn/article/details/108514770