我们把观察点先集中在一个大区内。在大多数情况下,一个大区内都会有多组游戏服,也就是多个游戏世界可供选择。简单点来实现,我们完全可以抛弃这个大区的概念,认为一个大区也就是放在同一个机房的多台服务器组,各服务器组间没有什么关系。这样,我们可为每组服务器单独配备一台登录服。最后的结构图应该像这样:
loginServer gameServer| /
|/ client
该结构下的玩家操作流程为,先选择大区,再选择大区下的某台服务器,即某个游戏世界,点击进入后开始帐号验证过程,验证成功则进入了该游戏世界。但是,如果玩家想要切换游戏世界,他只能先退出当前游戏世界,然后进入新的游戏世界重新进行帐号验证。
早期的游戏大都采用的是这种结构,有些游戏在实现时采用了一些技术手段使得在切换游戏服时不需要再次验证帐号,但整体结构还是未做改变。
该结构存在一个服务器资源配置的问题。因为登录服处理的逻辑相对来说比较简单,就是将玩家提交的帐号和密码送到数据库进行验证,和生成会话密钥发送给游戏服和客户端,操作完成后连接就会立即断开,而且玩家在以后的游戏过程中不会再与登录服打任何交道。这样处理短连接的过程使得系统在大多数情况下都是比较空闲的,但是在某些时候,由于请求比较密集,比如开新服的时候,登录服的负载又会比较大,甚至会处理不过来。
另外在实际的游戏运营中,有些游戏世界很火爆,而有些游戏世界却非常冷清,甚至没有多少人玩的情况也是很常见的。所以,我们能否更合理地配置登录服资源,使得整个大区内的登录服可以共享就成了下一步改进的目标。
服务器结构探讨 -- 登录服的负载均衡
回想一下我们在玩wow时的操作流程:运行wow.exe进入游戏后,首先就会要求我们输入用户名和密码进行验证,验证成功后才会出来游戏世界列表,之后是排队进入游戏世界,开始游戏...
可以看到跟前面的描述有个很明显的不同,那就是要先验证帐号再选择游戏世界。这种结构也就使得登录服不是固定配备给个游戏世界,而是全区共有的。
我们可以试着从实际需求的角度来考虑一下这个问题。正如我们之前所描述过的那样,登录服在大多数情况下都是比较空闲的,也许我们的一个拥有20个游戏世界的大区仅仅使用10台或更少的登录服即可满足需求。而当在开新区的时候,或许要配备40台登录服才能应付那如潮水般涌入的玩家登录请求。所以,登录服在设计上应该能满足这种动态增删的需求,我们可以在任何时候为大区增加或减少登录服的部署。
当然,在这里也不会存在要求添加太多登录服的情况。还是拿开新区的情况来说,即使新增加登录服满足了玩家登录的请求,游戏世界服的承载能力依然有限,玩家一样只能在排队系统中等待,或者是进入到游戏世界中导致大家都卡。
另外,当我们在增加或移除登录服的时候不应该需要对游戏世界服有所改动,也不会要求重启世界服,当然也不应该要求客户端有什么更新或者修改,一切都是在背后自动完成。
最后,有关数据持久化的问题也在这里考虑一下。一般来说,使用现有的商业数据库系统比自己手工技术先进要明智得多。我们需要持久化的数据有玩家的帐号及密码,玩家创建的角色相关信息,另外还有一些游戏世界全局共有数据也需要持久化。
好了,需求已经提出来了,现在来考虑如何将其实现。
对于负载均衡来说,已有了成熟的解决方案。一般最常用,也最简单部署的应该是基于DNS的负载均衡系统了,其通过在DNS中为一个域名配置多个IP地址来实现。最新的DNS服务已实现了根据服务器系统状态来实现的动态负载均衡,也就是实现了真正意义上的负载均衡,这样也就有效地解决了当某台登录服当机后,DNS服务器不能立即做出反应的问题。当然,如果找不到这样的解决方案,自己从头打造一个也并不难。而且,通过DNS来实现的负载均衡已经包含了所做的修改对登录服及客户端的透明。
而对于数据库的应用,在这种结构下,登录服及游戏世界服都会需要连接数据库。从数据库服务器的部署上来说,可以将帐号和角色数据都放在一个中心数据库中,也可分为两个不同的库分别来处理,基到从物理上分到两台不同的服务器上去也行。
但是对于不同的游戏世界来说,其角色及游戏内数据都是互相独立的,所以一般情况下也就为每个游戏世界单独配备一台数据库服务器,以减轻数据库的压力。所以,整体的服务器结构应该是一个大区有一台帐号数据库服务器,所有的登录服都连接到这里。而每个游戏世界都有自己的游戏数据库服务器,只允许本游戏世界内的服务器连接。
最后,我们的服务器结构就像这样:
大区服务器 /| /
/ |/ 登录服1登录服2世界服1世界服2/| ||
/ || |帐号数据库 DBS DBS
这里既然讨论到了大区及帐号数据库,所以顺带也说一下关于激活大区的概念。wow中一共有八个大区,我们想要进入某个大区游戏之前,必须到官网上激活这个区,这是为什么呢?
一般来说,在各个大区帐号数据库之上还有一个总的帐号数据库,我们可以称它为中心数据库。比如我们在官网上注册了一个帐号,这时帐号数据是只保存在中心数据库上的。而当我们要到一区去创建角色开始游戏的时候,在一区的帐号数据库中并没有我们的帐号数据,所以,我们必须先到官网上做一次激活操作。这个激活的过程也就是从中心库上把我们的帐号数据拷贝到所要到的大区帐号数据库中。
服务器结构探讨 -- 简单的世界服实现
讨论了这么久我们一直都还没有进入游戏世界服务器内部,现在就让我们来窥探一下里面的结构吧。
对于现在大多数MMORPG来说,游戏服务器要处理的基本逻辑有移动、聊天、技能、物品、任务和生物等,另外还有地图管理与消息广播来对其他高级功能做支撑。如纵队、好友、公会、战场和副本等,这些都是通过基本逻辑功能组合或扩展而成。
在所有这些基础逻辑中,与我们要讨论的服务器结构关系最紧密的当属地图管理方式。决定了地图的管理方式也就决定了我们的服务器结构,我们仍然先从最简单的实现方式开始说起。
回想一下我们曾战斗过无数个夜晚的暗黑破坏神,整个暗黑的世界被分为了若干个独立的小地图,当我们在地图间穿越时,一般都要经过一个叫做传送门的装置。世界中有些地图间虽然在地理上是直接相连的,但我们发现其游戏内部的逻辑却是完全隔离的。可以这样认为,一块地图就是一个独立的数据处理单元。
既然如此,我们就把每块地图都当作是一台独立的服务器,他提供了在这块地图上游戏时的所有逻辑功能,至于内部结构如何划分我们暂不理会,先把他当作一个黑盒子吧。
当两个人合作做一件事时,我们可以以对等的关系相互协商着来做,而且一般也都不会有什么问题。当人数增加到三个时,我们对等的合作关系可能会有些复杂,因为我们每个人都同时要与另两个人合作协商。正如俗语所说的那样,三个和尚可能会碰到没水喝的情况。当人数继续增加,情况就变得不那么简单了,我们得需要一个管理者来对我们的工作进行分工、协调。游戏的地图服务器之间也是这么回事。
一般来说,我们的游戏世界不可能会只有一块或者两块小地图,那顺理成章的,也就需要一个地图管理者。先称它为游戏世界的中心服务器吧,毕竟是管理者嘛,大家都以它为中心。
中心服务器主要维护一张地图ID到地图服务器地址的映射表。当我们要进入某张地图时,会从中心服上取得该地图的IP和port告诉客户端,客户端主动去连接,这样进入他想要去的游戏地图。在整个游戏过程中,客户端始终只会与一台地图服务器保持连接,当要切换地图的时候,在获取到新地图的地址后,会先与当前地图断开连接,再进入新的地图,这样保证玩家数据在服务器上只有一份。
我们来看看结构图是怎样的: 中心服务器 // /// /登录服 地图1地图2地图n / |/ // |/ /客户端
很简单,不是吗。但是简单并不表示功能上会有什么损失,简单也更不能表示游戏不能赚钱。早期不少游戏也确实采用的就是这种简单结构。
服务器结构探讨 -- 继续世界服
都已经看出来了,这种每切换一次地图就要重新连接服务器的方式实在是不够优雅,而且在实际游戏运营中也发现,地图切换导致的卡号,复制装备等问题非常多,这里完全就是一个事故多发地段,如何避免这种频繁的连接操作呢?
最直接的方法就是把那个图倒转过来就行了。客户端只需要连接到中心服上,所有到地图服务器的数据都由中心服来转发。很完美的解决方案,不是吗?
这种结构在实际的部署中也遇到了一些挑战。对于一般的MMORPG服务器来说,单台服务器的承载量平均在2000左右,如果你的服务器很不幸地只能带1000人,没关系,不少游戏都是如此;如果你的服务器上跑了3000多玩家依然比较流畅,那你可以自豪地告诉你的策划,多设计些大量消耗服务器资源的玩法吧,比如大型国战、公会战争等。
2000人,似乎我们的策划朋友们不大愿意接受这个数字。我们将地图服务器分开来原来也是想将负载分开,以多带些客户端,现在要所有的连接都从中心服上转发,那连接数又遇到单台服务器的可最大承载量的瓶颈了。
这里有必要再解释下这个数字。我知道,有人一定会说,才带2000人,那是你水平不行,我随便写个TCP服务器都可带个五六千连接。问题恰恰在于你是随便写的,而MMORPG的服务器是复杂设计的。如果一个演示socket API用的echo服务器就能满足MMOG服务器的需求,那写服务器该是件多么惬意的事啊。
但我们所遇到的事实是,服务器收到一个移动包后,要向周围所有人广播,而不是echo服务器那样简单的回应;服务器在收到一个连接断开通知时要向很多人通知玩家退出事件,并将该玩家的资料写入数据库,而不是echo服务器那样什么都不需要做;服务器在收到一个物品使用请求包后要做一系列的逻辑判断以检查玩家有没有作弊;服务器上还启动着很多定时器用来更新游戏世界的各种状态......
其实这么一比较,我们也看出资源消耗的所在了:服务器上大量的复杂的逻辑处理。再回过头来看看我们想要实现的结构,我们既想要有一个唯一的入口,使得客户端不用频繁改变连接,又希望这个唯一入口的负载不会太大,以致于接受不了多少连接。
仔细看一看这个需求,我们想要的仅仅只是一台管理连接的服务器,并不打算让他承担太多的游戏逻辑。既然如此,那五六千个连接也还有满足我们的要求。至少在现在来说,一个游戏世界内,也就是一组服务器内同时有五六千个在线的玩家还是件让人很兴奋的事。事实上,在大多数游戏的大部分时间里,这个数字也是很让人眼红的。
什么?你说梦幻、魔兽还有史先生的那个什么征途远不止这么点人了!噢,我说的是大多数,是大多数,不包括那些明星。你知道大陆现在有多少游戏在运营吗?或许你又该说,我们不该在一开始就把自己的目标定的太低!好吧,我们还是先不谈这个。
继续我们的结构讨论。一般来说,我们把这台负责连接管理的服务器称为网关服务器,因为内部的数据都要通过这个网关才能出去,不过从这台服务器提供的功能来看,称其为反向代理服务器可能更合适。我们也不在这个名字上纠缠了,就按大家通用的叫法,还是称他为网关服务器吧。
网关之后的结构我们依然可以采用之前描述的方案,只是,似乎并没有必要为每一个地图都开一个独立的监听端口了。我们可以试着对地图进行一些划分,由一个Master Server来管理一些更小的Zone Server,玩家通过网关连接到Master Server上,而实际与地图有关的逻辑是分派给更小的Zone Server去处理。
最后的结构看起来大概是这样的:
Zone Server Zone Server
/ /
/ /
Master Server Master Server
/ / /
/ / /
Gateway Server / /
| / / /
| / / /
| Center Server
|
|
Client
服务器结构探讨 -- 最终的结构
如果我们就此打住,可能马上就会有人要嗤之以鼻了,就这点古董级的技术也敢出来现。好吧,我们还是把之前留下的问题拿出来解决掉吧。
一般来说,当某一部分能力达不到我们的要求时,最简单的解决方法就是在此多投入一点资源。既然想要更多的连接数,那就再加一台网关服务器吧。新增加了网关服后需要在大区服上做相应的支持,或者再简单点,有一台主要的网关服,当其负载较高时,主动将新到达的连接重定向到其他网关服上。
而对于游戏服来说,有一台还是多台网关服是没有什么区别的。每个代表客户端玩家的对象内部都保留一个代表其连接的对象,消息广播时要求每个玩家对象使用自己的连接对象发送数据即可,至于连接是在什么地方,那是完全透明的。当然,这只是一种简单的实现,也是普通使用的一种方案,如果后期想对消息广播做一些优化的话,那可能才需要多考虑一下。
既然说到了优化,我们也稍稍考虑一下现在结构下可能采用的优化方案。
首先是当前的Zone Server要做的事情太多了,以至于他都处理不了多少连接。这其中最消耗系统资源的当属生物的AI处理了,尤其是那些复杂的寻路算法,所以我们可以考虑把这部分AI逻辑独立出来,由一台单独的AI服务器来承担。
然后,我们可以试着把一些与地图数据无关的公共逻辑放到Master Server上去实现,这样Zone Server上只保留了与地图数据紧密相关的逻辑,如生物管理,玩家移动和状态更新等。
还有聊天处理逻辑,这部分与游戏逻辑没有任何关联,我们也完全可以将其独立出来,放到一台单独的聊天服务器上去实现。
最后是数据库了,为了减轻数据库的压力,提高数据请求的响应速度,我们可以在数据库之前建立一个数据库缓存服务器,将一些常用数据缓存在此,服务器与数据库的通信都要通过这台服务器进行代理。缓存的数据会定时的写入到后台数据库中。
好了,做完这些优化我们的服务器结构大体也就定的差不多了,暂且也不再继续深入,更细化的内容等到各个部分实现的时候再探讨。
好比我们去看一场晚会,
舞台上演员们按着预定的节目单有序地上演着,但这就是整场晚会的全部吗?显然不止,在幕后还有太多太多的人在忙碌着,甚至在晚会前和晚会后都有。我们的游戏服务器也如此。
在之前描述的部分就如同舞台上的演员,是我们能直接看到的,幕后的工作人员我们也来认识一下。
现实中有警察来维护秩序,游戏中也如此,这就是我们常说的GM。GM可以采用跟普通玩家一样的拉入方式来进入游戏,当然权限会比普通玩家高一些,也可以提供一台GM服务器专门用来处理GM命令,这样可以有更高的安全性,GM服一般接在中心服务器上。
在以时间收费的游戏中,我们还需要一台计费的服务器,这台服务器一般接在网关服务器上,注册玩家登录和退出事件以记录玩家的游戏时间。
任何为用户提供服务的地方都会有日志记录,游戏服务器当然也不例外。从记录玩家登录的时间,地址,机器信息到游戏过程中的每一项操作都可以作为日志记录下来,以备查错及数据挖掘用。至于搜集玩家机器资料所涉及到的法律问题不是我们该考虑的。
差不多就这么多了吧,接下来我们会按照这个大致的结构来详细讨论各部分的实现。
服务器结构探讨 -- 一点杂谈
再强调一下,服务器结构本无所谓好坏,只有是否适合自己。我们在前面探讨了一些在现在的游戏中见到过的结构,并尽我所知地分析了各自存在的一些问题和可以做的一些改进,希望其中没有谬误,如果能给大家也带来些启发那自然更好。
突然发现自己一旦罗嗦起来还真是没完没了。接下来先说说我在开发中遇到过的一些困惑和一基础问题探讨吧,这些问题可能有人与我一样,也曾遇到过,或者正在被困扰中,而所要探讨的这些基础问题向来也是争论比较多的,我们也不评价其中的好与坏,只做简单的描述。
首先是服务器操作系统,linux与windows之争随处可见,其实在大多数情况下这不是我们所能决定的,似乎各大公司也基本都有了自己的传统,如网易的freebsd,腾讯的linux等。如果真有权利去选择的话,选自己最熟悉的吧。
决定了OS也就基本上确定了网络IO模型,windows上的IOCP和linux下的epool,或者直接使用现有的网络框架,如ACE和asio等,其他还有些商业的网络库在国内的使用好像没有见到,不符合中国国情嘛。:)
然后是网络协议的选择,以前的选择大多倾向于UDP,为了可靠传输一般自己都会在上面实现一层封装,而现在更普通的是直接采用本身就很可靠的TCP,或者TCP与UDP的混用。早期选择UDP的主要原因还是带宽限制,现在宽带普通的情况下TCP比UDP多出来的一点点开销与开发的便利性相比已经不算什么了。当然,如果已有了成熟的可靠UDP库,那也可以继续使用着。
还有消息包格式的定义,这个曾在云风的blog上展开过激烈的争论。消息包格式定义包括三段,包长、消息码和包体,争论的焦点在于应该是消息码在前还是包长在前,我们也把这个当作是信仰问题吧,有兴趣的去云风的blog上看看,论论。
另外早期有些游戏的包格式定义是以特殊字符作分隔的,这样一个好处是其中某个包出现错误后我们的游戏还能继续。但实际上,我觉得这是完全没有必要的,真要出现这样的错误,直接断开这个客户端的连接可能更安全。而且,以特殊字符做分隔的消息包定义还加大了一点点网络数据量。
最后是一个纯技术问题,有关socket连接数的最大限制。开始学习网络编程的时候我犯过这样的错误,以为port的定义为unsigned short,所以想当然的认为服务器的最大连接数为65535,这会是一个硬性的限制。而实际上,一个socket描述符在windows上的定义是unsigned int,因此要有限制那也是四十多亿,放心好了。
在服务器上port是监听用的,想象这样一种情况,web server在80端口上监听,当一个连接到来时,系统会为这个连接分配一个socket句柄,同时与其在80端口上进行通讯;当另一个连接到来时,服务器仍然在80端口与之通信,只是分配的socket句柄不一样。这个socket句柄才是描述每个连接的唯一标识。按windows网络编程第二版上的说法,这个上限值配置影响。
一种高性能网络游戏服务器架构设计
服务器端则负责响应所有客户端的连接请求和游戏逻辑处理,并控制所有客户端的游戏画面绘制。客户端与服务器通过网络数据包交互完成每一步游戏逻辑,由于游戏逻辑是由服务器负责处理的,要保证面对海量用户登录时,游戏具有良好的流畅性和用户体验,优秀的服务器架构起到了关键的作用。
1.1 服务器架构分类
服务器组的架构一般分为两种:第一种是带网关服务器的服务器架构;第二种是不带网关服务器的服务器架构,这两种方案各有利弊。在给出服务器架构设计之前,先对这两种设计方案进行详细的探讨。
所谓网关服务器,其实是Gate服务器,比如LoginGate、GameGate等。网关服务器的主要职责是将客户端和游戏服务器隔离,客户端程序直接与这些网关服务器通信,并不需要知道具体的游戏服务器内部架构,包括它们的IP、端口、网络通信模型(完成端口或Epoll)等。客户端只与网关服务器相连,通过网关服务器转发数据包间接地与游戏服务器交互。同样地,游戏服务器也不直接和客户端通信,发给客户端的协议都通过网关服务器进行转发。
1.2 服务器架构设计
根据网络游戏的规模和设计的不同,每组服务器中服务器种类和数量是不尽相同的。本文设计出的带网关服务器的服务器组架构如图1所示。
本文将服务器设计成带网关服务器的架构,虽然加大了服务器的设计复杂度,但却带来了以下几点好处:
(1)作为网络通信的中转站,负责维护将内网和外网隔离开,使外部无法直接访问内部服务器,保障内网服务器的安全,一定程度上较少外挂的攻击。
(2)网关服务器负责解析数据包、加解密、超时处理和一定逻辑处理,这样可以提前过滤掉错误包和非法数据包。
(3)客户端程序只需建立与网关服务器的连接即可进入游戏,无需与其它游戏服务器同时建立多条连接,节省了客户端和服务器程序的网络资源开销。
(4)在玩家跳服务器时,不需要断开与网关服务器的连接,玩家数据在不同游戏服务器间的切换是内网切换,切换工作瞬间完成,玩家几乎察觉不到,这保证了游戏的流畅性和良好的用户体验。
在享受网关服务器带来上述好处的同时,还需注意以下可能导致负面效果的两个情况:如何避免网关服务器成为高负载情况下的通讯瓶颈问题以及由于网关的单节点故障导致整组服务器无法对外提供服务的问题。上述两个问题可以采用“多网关” 技术加以解决。顾名思义,“多网关” 就是同时存在多个网关服务器,比如一组服务器可以配置三台GameGate。当负载较大时,可以通过增加网关服务器来增加网关的总体通讯流量,当一台网关服务器宕机时,它只会影响连接到本服务器的客户端,其它客户端不会受到任何影响。
从图1的服务器架构图可以看出,一组服务器包括LoginGate、LoginServer、GameGate、GameServer、DBServer和MServer等多种服务器。LoginGate和GameGate就是网关服务器,一般一组服务器会配置3台GameGate,因为稳定性对于网络游戏运营来说是至关重要的,而服务器宕机等突发事件是游戏运营中所面临的潜在风险,配置多台服务器可以有效地降低单个服务器宕机带来的风险。另外,配置多台网关服务器也是进行负载均衡的有效手段之一。下面将对各种服务器的主要功能和彼此之间的数据交互做详细解释。
(1)LoginGate
LoginGate主要负责在玩家登录时维护客户端与LoginServer之间的网络连接与通讯,对LoginServer和客户端的通信数据进行加解密、校验。
(2)LoginServer
LoginServer主要功能是验证玩家的账号是否合法,只有通过验证的账号才能登录游戏。从架构图可以看出, DBServer和GameServer会连接LoginServer。玩家登录基本流程是,客户端发送账号和密码到LoginServer验证,如果验证通过,LoginServer会给玩家分配一个SessionKey,LoginServer会把这个SessionKey发送给客户端、DBServer和GameServer,在后续的选择角色以后进入游戏过程中,DBServer和GameServer将验证SessionKey合法性,如果和客户端携带的SessionKey不一致,将无法成功获取到角色或者进入游戏。
(3)GameGate
GameGate(GG)主要负责在用户游戏过程中负责维持GS与客户端之间的网络连接和通讯,对GS和客户端的通信数据进行加解密和校验,对客户端发往GS的用户数据进行解析,过滤错误包,对客户端发来的一些协议作简单的逻辑处理,其中包括游戏逻辑中的一些超时判断。在用户选择角色过程中负责维持DBServer与客户端之间的网络连接和通讯,对DBServer和客户端的通信数据进行加解密和校验,对客户端发往DBServer的用户数据做简单的分析。维持客户端与MServer之间的网络连接与通讯、加解密、数据转发和简单的逻辑处理等。
(4)GameServer
GameServer(GS)主要负责游戏逻辑处理。网络游戏有庞大世界观背景,绚丽激烈的阵营对抗以及完备的装备和技能体系。目前,网络游戏主要包括任务系统、声望系统、玩家PK、宠物系统、摆摊系统、行会系统、排名系统、副本系统、生产系统和宝石系统等。从软件架构角度来看,这些系统可以看着GS的子系统或模块,它们共同处理整个游戏世界逻辑的运算。游戏逻辑包括角色进入与退出游戏、跳GS以及各种逻辑动作(比如行走、跑动、说话和攻击等)。
由于整个游戏世界有许多游戏场景,在该架构中一组服务器有3台GS共同负责游戏逻辑处理,每台游戏服务器负责一部分地图的处理,这样不仅降低了单台服务器的负载,而且降低了GS宕机带来的风险。玩家角色信息里会保持玩家上次退出游戏时的地图编号和所在GS编号,这样玩家再次登录时,会进入到上次退出时的GS。
上面提到过,在验证账号之后,LoginServer会把这个SessionKey 发给GS,当玩家选择角色登录GS时,会把SessionKey一起发给GS,这时GS会验证SessionKey是否与其保存的相一致,不一致的话GS会拒绝玩家进入游戏。MServer的主要负责GS之间的数据转发以及数据广播,另外,一些系统也可以放到MServer上,这样也可以减轻GS的运算压力。
(5)DBServer
DBServer主要的功能是缓存玩家角色数据,保证角色数据能快速的读取和保存。由于角色数据量是比较大的,包括玩家的等级、经验、生命值、魔法值、装备、技能、好友、公会等。如果每次GS获取角色数据都去读数据库,效率必然非常低下,用DBServer缓存角色数据之后,极大地提高了数据请求的响应速度。
LoginServer会在玩家选组时把SessionKey发给DBServer,当玩家发送获取角色信息协议时会带上这个SessionKey,如果跟DBServer保存的SessionKey不一致,则DBServer会认为玩家不是合法用户,获取角色协议将会失败。另外,玩家选取角色正式进入游戏时,GS会给DBServer发送携带SessionKey的获取角色信息协议,这时DBServer同样会验证SessionKey的合法性。总之,只有客户端、DBServer和GS所保存的SessionKey一致,才能保证协议收到成功反馈。
与DBServer通讯的服务器主要有GG,GS和LoginServer,DBServer与GG交互的协议主要包括列角色、创建角色、删除角色、恢复角色等,DBServer与GS交互的协议包括读取角色数据、保存角色数据和跳服务器等,DBServer与LoginServer交互的协议主要是用户登录协议,这时候会给DBServer发送SessionKey。
(6)MServer
每一个组有一台MServer,主要负责维持3台GS之间数据的转发和数据广播。另外一些游戏系统也可能会放到MServer上处理,比如行会系统。
1.3 服务器交互的主要流程
下面给出服务器之间数据通讯的主要流程从这些流程能看出各种服务器之间是如何数据交互和协同工作的。
图2的流程说明了,在选角色过程中,客户端会把携带游戏账号和SessionKey的选角色协议发给GG,GG做一些简单处理之后转发给DBServer,DBServer要验证SessionKey的合法性,验证通过之后,DBServer会从角色信息缓冲区里取出该账户的所有角色信息发给客户端。这个过程在客户端的表现是,当选择好服务器组之后,客户端会直接显示该账号下的所有角色,之后就可以选择角色进入游戏了。
图3的流程说明了,在玩家选角色正式进入游戏时,客户端会把携带游戏账号、角色ID和SessionKey的登录协议发给GG,GG做一些简单处理之后转发给GS。GS会验证SessionKey的合法性,验证通过之后,GS会把验证通过的结果发给客户端,同时GS给DBServer发获取角色数据的协议,这些角色数据是一个玩家所有的游戏数据,包括装备、技能等等。
图4的流程说明了,在玩家游戏过程,客户端把逻辑协议(包括走、说话、跑、使用技能等)发给GG,GG完成加解密和简单逻辑处理之后转发给GS,GS负责这些协议的主要
逻辑处理。
2 总结
网络游戏服务器的架构设计已经成为当前网络游戏研究领域的热点,因为高性能服务器架构设计是一款网络游戏成功的关键。本文从实际应用出发,提出了一种高性能的服务器架构设计解决方案,并且详细探讨了各种服务器的功能,本文的最后给出了几个服务器之间数据通讯的关键流程,以图文并茂的方式解释各个服务器是如何协同工作的。
游戏服务器架构系列 - 网关服务
继上一篇介绍了的分布式游戏服务器架构,后面的课程我们将对于架构中的每一种服务和具体技术细节进行详细介绍。
首先回顾下游戏服务器架构中的列出的服务,包括:
网关服务器
中心服务器
单区服务器
跨区服务器
镜像服务器
今天我们来介绍游戏服务器架构中至关重要的服务:网关服务器。
服务描述:即用于维持玩家客户端的连接,将玩家发的游戏请求转发到具体后端服务的服务器。
功能特性:
1. 对外开放:即客户端需要知道网关的IP和端口,才能连接上来;
2. 统一入口:架构中可能存在很多后端服务,如果没有一个统一入口,则客户端需要知道每个后端服务的IP和端口。
3. 请求转发:由于统一了入口,所以网关必须能将客户端的请求转发到准确的服务上。
4. 无感更新:由于玩家连接的是网关服务器,只要连接不断;更新后端服务器对玩家来说是无感知的,或者感知很少(根据实现方式不同)。
一般情况下,有了以上4个特性,这个网关就可以用了。
但是如果只有上面4个特性,我们用Nginx做为网关也是可以的,为什么还需要自己做网关?
因为我们的游戏网关还需要具备以下特殊功能:
特殊功能:
1. Session认证:即能维护客户端是否登录成功的状态,对于未登录的请求,不予以转发,从而预防恶意攻击。
2. 流量限流:游戏经常会遇到DDOS攻击,一个客户端可以通过一个for循环一直给你发请求,所以必须进行限制。
3. 踢下线:游戏维护时,为了让玩家能更新补丁,会将玩家踢下线,重新走一遍登录流程,避免客户端与服务端的数据不一致,造成显示上的BUG。此外客服也需要经常对一些违规的玩家进行踢下线处理。
4. 在线统计:为什么网关来做在线统计呢?因为只有它有所有的玩家连接信息,所以可以轻松统计当前有多少玩家在线。
5. 协议加密:为了避免客户端的恶意攻击,我们需要将请求进行加密,由于统一了入口,所以网关来做非常容易。
6. 心跳检测:用于检测客户端是否已经掉线,一般超过几分钟没有收到心跳请求,则认为客户端已经掉线,直接请求登录数据,让玩家重新走登录流程。
集成以上的功能后,便形成了以下网关服务架构图:
网关服务架构图
这张图中的路由控制,会根据不同游戏类型会有所变动,其中:
1. 后端服务路由表:维护了后端当前有哪些服务注册到网关了,以及服务对应哪些区服的配置信息。
2. 区服注册表:维护了当前开了哪些区服及区服信息。
3. 终端管理:所有连接上网关的设备或进程都被认为是一个终端,终端会有一个编号,这个编号对应后端服务编号或者玩家编号。当需要转发消息给后端服务或客户端时,就需要从终端管理里找到具体的连接进行消息发送。
接下来用图示介绍一下,客户端、网关服务器、后端服务器的交互流程:
客户端与网关、后端服务的交互流程
Step1:客户端连接网关服务器,然后发送登录请求给网关;
Step2:网关直接将登录请求转发给对应区服的后端服务器进行登录验证;
Step3:后端服务器验证成功后,会返回登录信息给网关;
Step4:网关会根据登录信息维持一个Session映射,用于安全验证和重登判断,然后转发登录信息给客户端;
Step5:客户端收到登录成功消息后,就可以发送业务请求给网关了;
Step6;网关收到业务请求后,会先判断玩家是否登录过,登录过的才转发给后端服务器,并且在协议头增加玩家标记;
Step7:后端服务器收到业务请求后,根据协议头的玩家标记,找到玩家的数据进行业务处理,然后返回给网关;
Step8:网关收到业务回复后,直接返回给客户端;
PS:上面涉及到的一些关键技术细节,如:流量限流、协议加密,将会在后续的文章中详细介绍算法。
游戏服务器架构系列 - 网关限流
2018.10.30 10:00* 字数 830 阅读 150评论 0喜欢 3
为什么要进行网关限流?
在前面我们介绍的游戏服务端架构中,客户端通过Socket连接直连网关,所有请求都需要经过网关,然后由网关统一进行转发,为了避免玩家的DDOS攻击,所以需要在网关进行限流。
常见的算法主要有计数器限流、令牌桶限流和漏桶限流,这些算法都是单机的算法,正好可以用在网关限流。
算法
1、计数器限流
严格意义上来说计数器限流不属于限流算法,使用计数器来进行限流,主要用来限制总并发数,比如数据库连接数;只要全局总请求数或者一定时间段的总请求数设定的阀值则进行限流,是简单粗暴的总数量限流,而不是平均速率限流。
2、令牌桶算法(Token Bucket)
令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。令牌桶算法的描述如下:
假设限制1秒钟生成2个令牌,则按照500毫秒的固定速率往桶中添加令牌;
桶中最多存放a个令牌,当桶满时,新添加的令牌被丢弃或拒绝;
当有n个请求到达,将从桶中删除n个令牌,接着请求被发送到网络上;
如果桶中的令牌不足n个,则不会删除令牌,该请求将被限流(要么丢弃,要么缓冲区等待)。
令牌桶算法(图片来自网络)
3、漏桶算法(Leaky Bucket)
漏桶算法是一个存放固定水滴的桶,按照固定速率流出水滴,可以用于流量整形和流量控制,漏桶算法的描述如下:
一个固定容量的漏桶,按照常量固定速率流出水滴;
如果桶是空的,则不需流出水滴;
可以以任意速率流入水滴到漏桶;
如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃,要么缓冲区等待),而漏桶容量是不变的。
漏桶算法(图片来自网络)
对比
令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;漏桶是按照固定速率从桶中流出请求,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝
令牌桶限制的是平均流入速率,允许突发请求,只要有令牌就可以处理,支持一次拿5个令牌,6个令牌,并允许一定程度突发流量;漏桶限制的是常量流出速率,即流出速率是一个固定常量值,比如都是2的速率流出,而不能一次是2,下次又是3,从而平滑突发流入速率
基于Golang的算法实现:https://github.com/MaxwellBackend/Games/tree/master/ratelimit
游戏服务器架构系列 - 网关协议加密
2018.11.05 14:58 字数 734 阅读 123评论 0喜欢 1
一、概要
网关在游戏服务器架构中充当着很重要的角色,根据不同类型项目承担的功能也不一样,主要的功能有以下几种:
1. 压缩:压缩是一件比较耗时操作,放在网关可以减轻游戏压力;
2. 过滤:过滤主要是识别非正常请求,保护后端服务;
3. 转发:转发是保证客户端消息准确快速到达目标地址,加密来提高客户端与服务器之间通信安全;
4. 加密:这篇文章主要介绍一下游戏加密相关的技术。
二、加密算法的分类
加密主要提高破解通信协议的难度,避免协议被识别篡改,加密的技术有很多,大致可以分为以下几种:
1. 可逆加密
1.1 对称加密:DES、AES、RC4等;
1.2 非对称加密 :RSA等;
2. 不可逆加密:如:md5等;
三、加密流程
游戏网关通信协议采用可逆加密,使客户端可以解密协议,让客户端与服务器可以安全通信,加密的一般流程如下
1. 握手阶段,基于RSA算法
2. 交换KEY,基于DH算法
3. 数据流加密与解密,可采用:AES,RC4等算法
握手阶段(RSA)
客户端与服务器握手阶段主要是验证服务器真伪,这一步可以用RSA来实现(可以省略)
1. 服务器随机一段串A
2. 务器用客户端公钥加密发送客户端
3. 客户端收到后用客户端私钥解密出串A
4. 客户端用服务器公钥加密串B和解密后的串A,发送给服务器
5. 服务器收到后用服务器私钥解密串A和之前服务器生成的对比验证,ok则握手成功
交换KEY(DH)
1. 客户端通过Diffie-Hellman算法生成服务器加密,解密种子(服务器加密种子是客户端解密种子,服务器解密种子是客户端加密种子),
2. 服务器通过Diffie-Hellman算法根据加密,解密种子生成加密,解密KEY,客户端加密,解密种子
3. 客户端根据加密,解密种子生成KEY
数据流加密与解密
用协商过的KEY,进行数据流加密与解密(aes,rc4)
可以根据需要选择不同的加密算法,安全性高的可以选用AES,数据长度不变的可以选用RC4,
AES和RC4在所有算法中都是比较高效的算法,RC4效率好于AES,但安全不如AES,
对AES,DES,RC4几种算法使用案例及性能对比
Github实现:https://github.com/MaxwellBackend/Games/tree/master/security
有疑问加站长微信联系(非本文作者)