MMORPG大世界服务器技术浅探——服务器框架
[ 发表时间:2020-12-23 17:58:41 信息来源:九剑网络 ]
来源:https://zhuanlan.zhihu.com/p/266573590
本文从传统MMO游戏服务器架构说起,着手介绍在MMO无缝大世界这一前提下所面临的挑战和可用的一些服务器基础架构。这儿对读者所需游戏服务器开发知识不做过多假设,但仍需了解网络协议、存储和分布式数据同步基础。限于个人水平有限,错漏在所难免,愿技术同好多多交流。
传统MMORPG服务器架构
传统MMORPG服务器组简化示意如下图所示
图1
为便于理解,图中未涉及登录、充值、DB 缓存/存储和跨服相关的服务器,是因为在无缝大世界的实现中这些服务要么已有已经成熟的解决方案( 如DB相关)、要么其技术复杂度完全和是否大世界正交(如登录和充值服务),要么在大世界体系中它是不应该存在的服务器(如跨服服务器)。
为保持服务器名称与其内涵统一,故对图1中服务器的功能边界说明如下
•Gate Server : 网关服务器,它的主要功能为接受客户端连接、客户端/其它服务器之间的消息透传及对外网隔离游戏功能服务器。客户端只需要知道Gate Server的地址并和它进行通信,游戏功能服务器亦如是。
•World Server: 世界服务器,它的主要功能分为两部分,非游戏场景相关逻辑功能(如社交系统、聊天、部分社交任务等等)和游戏场景服务器的管理功能(场景服务器发现、健康检查、启动/关闭等)
•Zone Server : 游戏场景服务器 ,它的主要功能为游戏除World Server负责外的所有功能,如角色移动与位置同步、技能战斗、AI、寻路、大部分任务系统功能等。
这儿需要说明的是,Zone 0和 Zone N分管的是不同的游戏场景或游戏服务副本,这些场景之间有明确的边界,游戏角色在地图边界的一边完全不会感知到边界另一边的游戏角色和游戏内容,即不同Zone服务器上的玩家之间彼此之间的场景玩法及位置等信息完全隔离。
如下图所示,游戏地图共分为6个Zone (A~F),其中Zone A中有玩家a(标红),它的热区范围(AOI)被蓝色圆圈所覆盖。
图2
在经典MMORPG服务器架构中,因为Zone之间的隔离性,玩家a只看到了蓝色圈中变蓝的那些玩家,他看不到Zone A之外其它Zone中被AOI实际位置所覆盖的玩家,如位于Zone C中的玩家b。当玩家从跨越Zone的边界,他就会从原来Zone的玩家视野中消失,进而出现在新进入Zone的玩家视野,即一个玩家不可能同时出现在两个Zone玩家的视野,故两个Zone上的玩家之间也不可能发生战斗。
当游戏存在M个区服的时候,简化的服务器架构如下图所示
图3
抛开运营商业策略的原因,传统MMORPG之所以在技术层面需要分区分服,其主要原因有:
•AOI范围内的计算和消息复杂度和该范围内所覆盖的Entity(包括玩家)的个数的平方成正比
•单台游戏服务器硬件所能提供的计算量、网络带宽、存取IO速率有限,故其只能容纳有限数量的Entity仿真
故其策略是预估单个区服和单地图可容纳的人数上限,把玩家隔离在不同的区和不同的地图服务器以保证游戏体验的流畅性。
经典游戏服务器架构的优点:
•部署简单,进程数量少,按MMO的区服容纳人数标准,上述服务器进程除了网关、付费、DB服外大多可以部署在一台物理机上,容易做到一键发布。
•开发门槛低,游戏场景服务器功能高度内聚,游戏主体逻辑可运行在单线程顺序环境下,不需要考虑复杂异步通信和多线程的不确定性
•部分无状态服务器(Gate , Pay ,Login)或对延迟不敏感的服务器(World)虽然在架构上是单点,但扩展为高可用的分布式服务器的成本和难度很低。
•独立Zone用于跨服的功能易于实现——在Server Group之间加上跨服消息转发的服务器即可以实现相关功能
•配合游戏运营过程中的合区功能易于实现,在角色及道具ID生成和玩家可输入的唯一标示(如角色名,公会名等)时候把区服信息纳入考虑或做为标识头的一部分即可解决运营中后期数据合区时DB表项冲突的绝大部分问题。
无缝世界
在无缝世界里,玩家无法感受到地图边界的存在,只要在客户端地图连接的地方,就可以发生基于地图的交互、对战和PVE协作。我们仍考虑图2所示的服务器分区及玩家分布,试以此说明无缝世界和经典分区分服世界之间的区别。玩家a的AOI范围更新如图4所示:
图4
图4中的玩家a的AOI同步集合包含了全体蓝色和绿色玩家,在这样的无缝世界中的玩家a和b已经冲破Zone之潘篱,变得相互可见,玩家的AOI范围和Zone边界不相关,服务器Zone的边界对玩家来说已荡然无存。显然,无缝世界是更符合人类对这个世界基础的连续性的生理和心理认知。
既然无缝需要解决不同Zone服务器之间的实时可见和可交互,那么是不是可以有以下思路:
问题1 :我们让相邻接地图块之间的Zone进行相互连接进行消息交换和数据同步是不是就能实现无缝世界了?
我们将以某一具体的应用场景为基础,来考察分析要达到无缝这一目标所带来的游戏服务器开发的技术复杂度,这个看起来简单的问题往往比大多数游戏开发的想象来得困难许多。
在经典MMORPG中,同一个Zone服务器中的玩家a对玩家b使用瞬发单体攻击这一最简单的战斗操作的生效机制,其时序图简化如图5所示
图5
时序图中涉及6条消息及1条Zone服务器上的自操作流程:
1.玩家a客户端本地先预判攻击CD是否结束,玩家b是否是可攻击对象、是否在玩家A的攻击范围内等基础条件,在满足攻击技能释放条件时发送攻击消息给网关服务器Gate0
2.网关服务器Gate0把消息透传给Zone A ,中间可能涉及到的消息路由及消息头修改在时序图中略去不提
3.Zone A在收到Gate 0 转发的攻击消息后,做技能条件的所有判断(包括重复玩家A在本地所做的所有判断),如果技能触发条件判断失败,会返回错误消息给玩家a,否则使用普攻的目标筛选器对玩家b进行目标筛选,在筛选成功时计算普攻技能所造成的伤害,在计算完伤害数值之后把技能施放结果以消息的消息回传给a玩家,把受击消息传给b玩家。
4.技能施放结果消息(不管成功与否)都会发给Gate 0,目的地为玩家a
5.网关服务器Gate 0把施放结果消息透传给玩家a ,中间涉及到的消息头修改等略去不提
6.受击消息发给Gate0 ,目的地为玩家b
7.网关服务器Gate 0把受击消息透传给玩家b ,中间涉及到的消息头修改等略去不提
很容易看出上述流程中最关键最复杂的部分是第3步。这儿为什么Zone A对技能施放条件和目标进行再一次判断,原因如下:
•数据和逻辑以服务器为准,防止玩家a在自己客户端作弊伪造不满足条件的消息攻击服务器和其他玩家,以获取不正当的游戏收益,破坏游戏的平衡性
•a本地的数据只是完整游戏的子集,其本地进行技能施放时所判断的条件不完整
•网络游戏本质上是一分布式系统,在技术上游戏的数据绝对的以服务器为准,客户端的数据往往只是过往某一时刻数据的一个镜像,数据同步因网络延迟和帧率的原因存在一个恒大于0的时间差,这一时间差在大多数MMORPG中控制在150 ms以内。玩家a在本地判定技能条件的时间点数据可能为服务器在t0时刻的数据,而攻击消息到达服务器的时刻可能为t0 + X ,在后续这X时长内数据会发生变化,t0+X时刻的数据有可能已不满足技能施放条件。常见的情形如:玩家a在 t0 + Y ( Y <= X )时已经进入不可控状态,如眩晕、迷惑、或玩家b在t0 + Z ( Z <= X )时已经转为不可攻击状态,如下线、死亡、无敌等等
接下来考虑图4中所示的玩家a和玩家b,两者跨越了Zone服务器(Zone A和Zone C)的情形下a使用瞬发单体攻击对b进行进攻的情形。为便于直观分析,我更新一下服务器架构图局部(图6)
图6 扩展经典MMORPG服务器架构到无缝世界架构示意
图6所示的是一个极简化的无缝世界服务器架构的一种可能局部结构。世界被无缝的划分为许多个sub world,每个sub world管理一部分地图分块,zone与zone之间,sub wolrd与sub world之间均允许通过gate代理进行自由通信。图中玩家a和玩家b分属不同的sub world。在这一假设架构的前提下玩家b攻击玩家b执行的最基础的时序(不包含攻击行为的AOI,因为其等同于向玩家b所发送的最终消息),如图7所示
图 7,无缝世界的单体普攻时序的一种可能性
可以看到消息时序由原来的7步消息变成了21步,经典MMORPG中的第3步被拆成了10几个步骤:因为异步存在时间差的原因,a是否满足技能触发条件被判断了两次,b是否可以受击同样也判断了两次,受击效果计算在zone A和Zone C上分别进行了计算且计算结果还需要进行双方校正。上述时序我还把所有异常或消息顺序(如果消息走UDP)都未考虑在内,如果画出所有异常情形,可能其时序还能增加不止一倍的执行步骤。
上述时序也可以稍做优化,以简化太过复杂的逻辑时序。
优化方案A:在步骤7的b可受击判定中同时把b的属性从Zone C同步给Zone A,这样余下的步骤可以省去6步并省略掉第二次的b受击判定和余下的技能结果修正流程。但流程依旧有15步,比起在同一个Zone中执行攻击的流程所需的时序流程增加不止一倍。更为要命的是,这种复杂度对每个类似的战斗、交互流程都相同,这就需要每一个服务器开发人员都要能自己成体系的考虑分布式数据同步的所有步骤并做异常处理,这对开发人员的要求就太过苛刻了。显然地,问题1(让相邻接地图块之间的Zone进行相互连接进行消息交换和数据同步)这般简单粗暴的处理方式做不出可维护的无缝世界的服务器。
优化方案B:另一个针对图7所示时序的优化方案是每个Zone都保存其所管辖地图连接的其它Zone的一部分Entity的代理,所保存的这些Entity的位置处在比游戏AOI范围要更大一些,以便于做玩家的位置预测,图示如图8
图8
如图8所示,Zone A不光保存了属于自己的浅蓝色Entity信息,也以代理形式保存了属于Zone B ,Zone C, Zone D的亮紫色Entity的代理数据。Zone A对自己所管辖的Entity有读写权限(如Entity a),但它对代理的Entity仅拥有只读权限(如Entity b),玩家b的数据定期会从Zone C更新给Zone A,以保持b数据的最终一致性。使用这种冗余数据的方式进行处理之后,玩家a攻击玩家b的时序简化为图9
图9
如图9所示,a攻击b在严格意义下所需的步骤为14步,比优化方案A仅少付出1步。但注意到Zone A上已经存在玩家b的数据只读副本且考虑到玩家数据变化频率较低,故实际上大多数时候,步骤10所回传的Zone C伤害校正信息往往和在Zone A中所计算基本一致,这也就是说如果我愿意再多牺牲掉一些数据一致性,就能进一步增强游戏对玩家而言的其可用性。 在步骤4之后增加技能使用结果预反馈,具体时序如图10所示
图10
但优化方案B服务器逻辑程序在开发过程中所需考虑的数据同步的复杂度似乎相对方案A虽然已经简化许多,大多数情形之下 ,其可用性也大为增加(用户使用技能的反馈周期变短),但它所存在的根本问题是:对开发人员来说极不友好,其复杂度没有根本性的改观。
优化方案C: 可以很容易注意到问题复杂性的核心来源是分布式数据的一致性(Zone A中的Entity b的数据是前一时刻的数据,最新时刻的数据有所不同)。我们可以在不修改服务器架构的前提下,把Entity数据同步的细节封装在服务器底层框架中实现,而诸如技能等涉及到位置敏感数据操作只需要写成任务丢到异步执行队列执行,逻辑流程本身再额外投递一个数据同步的标识,其余部分可以与经典MMORPG中几乎完全相同。这样就能大大简化开发过程中的复杂度。简化后的时序如图11所示
图 11
如图11所示,橙色部分为服务器在框架底层所实现的分布式数据同步,它隐藏了数据同步在算法和消息上的复杂性。事实上橙色部分的数据同步并不需要每次都发生,假如用户使用技能的时刻t0落后于当前已同步的的玩家b的时刻t1 ,则不会发生数据同步,或者服务器提高对数据一致性的容忍度来降低同步频率(如服务器逻辑帧率为18fps ,容忍3 fps的数据延迟,则数据可以220ms(55ms/帧 *4 )左右同步一次,而不是每帧有技能执行时都去请求某一些对象的数据同步)。
注意到异步执行的位置敏感性流程中可能需要等待其它服务器的消息返回才能继续往下走,这个流程似乎是为协程所量身定制。有了协程,代码逻辑可以一套写下去而不使用异步消息的Handle,代码可读性更佳。
健壮性与可用性
上一节所讨论的无缝世界基一个简单的服务器架构,当我们用游戏服务器集群来看待它的时候,它或许可以正常工作,但当我们用分布式系统来看待它的时候,大概会觉得它还只是个婴儿:
•负载均衡:对于静态划分的Zone服务器进度,而玩家却又可以自由的Zone之间流动,很可能在某一时刻某个Zone A进入了海量的玩家,而Zone B,C,D空无一人,服务器资源的有效利用率低
•过载保护: 上述示例中Zone A因为存在玩家可多的可能,其会导致Zone A的过载而不服务对玩家不可用
•可扩展性:因为Zone、Gate、World都还是静态的对应关系,其扩展性只能体现在新加一整组服务器层面,而不能单独增加某一类服务器用于缓解这类服务器的资源紧张情况,其可扩展性不足
•容错性:Gate ,World对于某一组地图分块来说都是单点部署,一台SubWorld Crash的后果是一大群人集体掉线,具有典型的单点故障问题
•容灾:上述架构在某个服务器、某个服务器集群、某个IDC机房发生异常时,不能快速为用户切换线路,保证服务的持续可用性,只以能以其它手段尽快恢复服务