场景集群的划分思路

      作者:dlite@163.com

      我们设计的MMO游戏中,计划对跨服务器的大型场景支持。也就是说,当一个游戏场景所需的计算需求超过单个服务器承载能力时,我们要支持服务器集群,并发进行处理。此处的计算需求,是指广义上的计算需求,包括CPU、存储空间和IO等各方面计算需求。

      这样的问题,本质上是分布式并行计算的问题。从并行计算方面考虑,关键要做的是问题的合理划分;从分布计算方面来考虑,关键要保证的是分布对象状态的一致性。 这里主要从几个方面讨论问题的划分。

      1)局部性特征

      虽然一个场景可能很宏大,会包含数量巨大的游戏对象,但每个对象和环境及其他对象的及时交互都是限定在一定范围的。例如,一个玩家只能看到屏幕可视范围的其他对象;一个NPC的AI计算,取决于它可交互区域内的其他对象状态;等等。

     正是由于MMO游戏的这种局部性特征,使得我们可以把一个大的场景及其中的对象按照地理位置拆分成更小的计算单元。

      2) 问题划分

      通常的方法是给场景地图打格子,把大场景等分成多个小的区域,例如二维的正方形区域。我们暂先称其中的一个格子为一个单元格(Cell)。每个单元格都包含一定数量的游戏对象。例如,PC、NPC、掉落物和障碍物,等等。

      为描述方便,我们称每台服务器为一个节点(Node)。单元格被看作是并行计算负载的一个划分,场景中的单元格被分配到的各节点中,每个节点负责处理一个或多个单元格。

      需要注意的是,这种划分本质上是对先对计算环境进行划分,进而对计算负载进行划分,并不是直接对被计算负载的划分。在游戏的服务器端逻辑中,真正的运算目标是各种具体的游戏对象(如PC、NPC和物品),所在单元格只是这些对象的位置属性。然而,由于前面叙述的游戏局部性特征,位置上临近的游戏对象往往会共享信息。

      如果我们不考虑位置,直接将不同游戏对象划分到不同的节点中进行计算,很容易造成大量对象在不同节点上的多份拷贝,丧失了集群计算的优势。 这是因为这些游戏对象往往彼此相互关联,要对一个对象进行计算,往往要获取周围相关对象的信息。例如,考虑一个游戏角色R捡起一个装备E。假设R位于节点N1上,E位于节点N2上。我们在N1上计算调整R的背包信息时,必然要知道E的信息,这就需要节点N1同时持有R和E的信息;反之亦然。

      如果我们依据单元格的方式来划分,要复制的对象,通常只剩下共享边界上的了。

      3)负载估算

      首先,从整体上分析游戏逻辑处理开销。

      实际中,服务器端游戏的计算主要包括消息处理、碰撞检测、AI逻辑和游戏对象管理。其中,消息处理主要是客户端到服务器端的消息,其次是服务器之间的消息,这一部分负载比较好估算,可以近似认为是和线玩家角色的数量成正比。除消息处理之外,其余的计算量是游戏对象(玩家角色、NPC、物品和障碍物,等等)的递增函数。其中最主要的计算量,也集中在和玩家角色交互的对象上。因此,为简单起见,这几类计算也可以近似为和玩家数量成正比。

      除了主要的游戏逻辑处理外,对于集群系统,数据的共享和复制也会有一定的开销。且如果考虑高可用性(如单个服务器崩溃,导致故障切换和负载转移),数据要有一定的冗余。按照经验,我们尽量把这部分计算开销限制在总体开销的20%以内。

      因此,和单游戏逻辑服务器架构相比,集群服务器组架构中,每台服务器的实际玩家容量是前者的80%。

      接下来,分析前面所述的打格子方式的处理开销。

      假设我们的单元格是平面正方形的,每台服务器至少要关注9个格子(中心的一个,和相邻的8个)。对于统一计算强度的游戏,在同样的服务器配置情况下,如果单服务器架构的最多能容纳N个在线玩家,则集群服务器最多容纳N*80%个在线玩家。为了防止大量玩家聚集于一个点,超出服务器处理能力的情况,我们必须限制玩家的密度。考虑到服务器维护的区域按单元格划分,我们可以限制单个单元格的玩家数量。

      按照上面的分析,每个单元格最多容纳的玩家数为:0.8N/9=4N/45。例如,若单服务器架构中的每台服务器最多能处理3600个在线玩家,则服务器集群架构中的每个单元格的玩家容量为320人。当单元格中玩家数量接近这个数字的时候,我们就应该在客户端给出一定的提示,并从程序上限制更多玩家涌入这一区域。

      需要注意的是,以上分析的前提是把游戏逻辑相关的计算进行了横向切割,分布在不同的机器上。我们也可以对计算进行纵向分割,例如有专门的碰撞检测服务器,专门的AI服务器等。在进一步分析和设计中,我们可以根据需要,把横向和纵向的切割结合起来,不过复杂度会有较大的提升。类比CPU的设计,这里的横向切割可以看作是超标量结构,纵向切割可以看作是超流水线结构,两者的结合就是超标量超流水线结构。

      4)单元格的大小

      单元格的大小,主要取决于游戏对象的交互范围,进一步说就是两个对象的最大交互距离。例如,游戏角色的最大攻击范围。这是因为,我们要尽可能的让游戏对象的计算限制在较小的范围内。在对单元格内的对象进行计算时,我们要尽可能将其引用到的对象限制在不超过相邻单元格的范围内。这句话有点绕,还是举例说明吧。

      以前面的9宫格正方形划分为例,如果中间的单元格有一个游戏角色A攻击左边单元格中的游戏角色B,就会涉及到跨单元格的操作,如下图所示。



 
    B
A      C

  

      假设这两个单元格分别由不同的服务器管理,就会涉及到跨服务器的通信。事实上,相邻两个单元格的跨服务器通信基本上是不可避免的。我们的目的,就是让这种通信最多在相邻单元格进行,对于中间的单元格来说,最多格周围的8格单元格中的对象进行直接交互。例如,上图中的B和C直接不能直接相互攻击。

      因此,我们的设计要求是,在正方形划分中:游戏对象的最大直接交互距离 < 单元格的边长。因此,设计游戏时,我们需要约定游戏对象的最大直接交互范围

      另一方面,在格子大于最大交互距离的情况下,格子也不能太大。处于多台服务器边界的格子,需要共享复制。单元格越小,共享区域就越小,需要在服务器之间复制的信息越少。此外,在负载极大的情况下,服务器只负责处理一个单元格,单元格越小,单位面积上支持的玩家数可以多一些。

      通过以上对单元格上下限的分析,再结合游戏内容设计,就比较容易确定合适单元格的大小了。

      例如,假设游戏角色最大攻击范围是游戏中两个对象的最大交互范围,若这个值是18米,我们可以将单元格的边长设为20米,每个单元格的面积就是400平米。再结合上面“负载估算”的例子,每个单元格最多容纳320人,考虑到在一块区域中,有些地方是不允许玩家角色占据的(例如各种障碍物),而且通常一个单元格中的玩家站位也不是均用分布的(例如一个城堡的区域,可能大多玩家都集中在城门口),因此做这样的限制,在视觉表现上也是可以接受的。

      5)大型几何对象的碰撞检测

      为将位置上集中的对象计算控制在局部,理想的情况是:对一个单元格中的对象计算时,最多需要知道与之相邻的单元格中的对象信息。即“限定游戏对象的最大直接交互范围”,前面已经解决了与之相关的大多数问题。有一个比较特殊的问题是,游戏场景的大型几何对象,如山、河流、大型建筑物相关的碰撞检测,这些对象可能跨越多个单元格的范围。经过讨论,我们的结论是,对这些对象的碰撞线由程序按照单元格边界自动进行拆分,从而避免了此类信息的跨服务器复制。

      6)客户端的可视范围

      另一个问题是客户端可视范围与服务器端单元格的关系。由于可视范围可能远远大于单元格的尺寸,也远远大于游戏对象的交互范围。因此,如果要求画面上能表现很远处(超过玩家角色所在九宫格)的非静态游戏对象,相关数据可能会来源于多个游戏逻辑服务器。本质上,要传送给客户端的单元格区域个数,只取决于客户端要求画面表现的细致程度。

      一种简化的处理方式是,服务器端只发给客户端玩家角色所在九宫格的游戏对象信息。当然,这种优化方式,就把可视范围和单元格大小的划分给耦合起来了。

     本文从并行计算划分的角度出发,分析了游戏逻辑服务器集群设计的主要问题及解决思路。在后续的文章中,还会讨论其他相关问题,如分布式对象共享、数据传输、负载均衡和故障切换等。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值