1. 如何表现即时战斗效果?
2. 如果支撑5000玩家?
前者体现在画面效果和战斗节奏上,主要与渲染有关,不属于本文讨论的重点。本文主要探讨第二个问题,即架构上的考量。
第二个问题之所以是一个挑战,是因为存在着这样一种矛盾,即单服的运算能力不能满足游戏对运算能力的需求。
相对于回合制游戏,采用即时战斗机制的游戏需要实时计算更多的数据,例如在回合制游戏中,回合发招完成后才开始计算伤害效果,而即时战斗类游戏会在持续播放战斗动画的过程中不断产生计算需求,只要播放到关键帧就要计算伤害效果。这样,即时战斗类游戏计算伤害效果的间隔更短,频率更高,而对CPU的运算能力提出了更高的要求。在最近几年,计算机技术的发展很快,可以用日新月异来形容,虽然双核、多核、多CPU的计算机正逐步取代单CPU的计算机,但仍尚未成为市场主流,而让单CPU配置的主流服务器支撑这么多玩家即时战斗多少还是会有些力不从心。因此如何进行游戏服务器集群的架构,从而有效地利用目前主流机型为《大唐豪侠》服务,就成为了一个挑战。
在游戏架构里,游戏的同步策略是很重要的部分,它关系到游戏运营时对网络带宽的要求。同步策略越优秀,网络带宽的耗用就越少,游戏过程中的流畅度也会越好。《大唐豪侠》的总监赵青先生曾撰文详细阐述过同步策略方面的问题,所以本文就不在这里重复了。本文着重探讨服务器构架过程中,面对各种技术方案,我们是如何进行考虑和选择的。
技术选择
从宏观的层面看,我们主要考虑了以下几个方面的问题。但总的原则相当清晰:“简单就是美”,KISS(Keep It Simple,Stupid)。这一原则同时兼顾到了项目的进度和远景目标。
单服/多服
在当前主流服务器配置的情况下,要支持5000人同时在线,就只能选择多服务器架构,单服务器无法胜任这样的要求。事实上,分布式的服务器架构还给我们带来了易扩展的好处。只要我们在设计游戏服务器时,让游戏服务进程(GAS,Game Server)做完全相同的事情,那么就能很容易地加入新的服务器。新服务器的加入,既可以在一定程度上提高支持同时在线玩家的能力,也可以分担其它服务器的负担。
多线程/多进程
通常,用多线程编程方式构建应用程序,是利用多核或多CPU计算机性能的主要方式。但用多线程方式开发游戏服务器并不是非常适合。
众所周知,成功的游戏离不开丰富的玩法,玩法越丰富,越能吸引玩家,玩家也越不容易感到枯燥,但这样一来,游戏的逻辑也会变得更加复杂。此外,游戏的输入主要来自玩家,玩家使用物品或者使用技能都可看作是对系统的输入。另外,由于存在着用户之间的交互,因此一个玩家的行为往往对其它玩家存在着影响。这些因素的存在,增加了BUG出现的几率。由于硬件和操作系统的原因,线程切换时间不完全一致,在这种情况下,要对某些BUG进行复现则相当困难。
综合这些分析,可以简单地说,以多线程方式开发,既有开发方面的难度,也有维护方面的难度。相比而言,多进程方式既可以满足多服务器结构的要求,在一定程度上还规避了某些多线程的问题。
大容量/小容量
通常,我们会希望在玩家视野所及的地方越热闹越好,这样,人气就会很旺。那么是不是在每个游戏服务进程中,支持的玩家也是越多越好呢? 实际上并不完全如此。 所谓玩家至上,是指让玩家在游戏进行的过程中能终保持良好的游戏感觉。我们知道对于复杂程序来说,完全消灭BUG几乎是不可能的,游戏服务进程会因为各种BUG或其它各种原因而崩溃、宕机。一个游戏进程中的玩家越多,一次宕机影响到的玩家数量也会越多。如果排除该问题需要很长的时间,而游戏进程又连续崩溃,那么游戏玩家的耐心和热情将会受到很大的打击。 显然,我们是无法完全避免BUG的,也无法完全避免宕机,但是我们可以努力降低这些问题带来的负面影响,使受影响的玩家保持在一个较少的数量上。这样看来,一个进程中容纳的玩家数就不应该太多,从目前的主流服务器配置来评估,单个CPU支持1000人是比较合理的数量级。
无缝地图/多地图
国外几款游戏大作的场景多是无缝拼接起来的,以《魔兽世界》为例,当玩家从西大陆进入东大陆时,会经过一个狭长的谷道,通过谷道需要几十秒时间,客户端有充足的时间在玩家行走过程中加载目的地的地图信息。没有了显式地切换场景,玩家的感受可能会更连贯,但是在技术实现上,无缝地图方式还有一些其它方面的因素要考虑,它是一个综合问题,实现起来会比较复杂。
而在多地图方式下,从一个场景到另外一个场景,要通过NPC对话或者经过TRAP点显式控制场景切换,例如玩家如果想从新手村到长安,就要和驿卒对话,选择要去的目的地,玩家选择长安后,在客户端,通常会显示重新加载地图文件的进度条,根据地图文件的大小和实现技术的不同,加载一般需要几秒到十几秒不等。
虽然多地图方式比无缝地图方式在表现上要稍差一些,但多地图方式的技术实现相对简单,同时,也会简化和规避一些其它的技术问题。例如如果是无缝地图方式,开发使用的地图编辑器就需要能支持多人同时修改同一个地图文件。
动态/静态负载均衡
在分布式架构中,负载均衡是必须要面对和考虑的问题。 某些场景或者场景中的某个区域,可能会因为多种因素的综合效应聚合起相当数量的玩家,例如在门派入口处,玩家会自发的在这里摆摊交易,形成商业区;又如战场中的攻防要塞,是攻守双方争夺的焦点,也是玩家聚集的地方。如何才能保证在这种玩家聚集的情况下,仍然能让他们感觉流畅,玩得畅快呢?这就需要考虑服务器的性能上限,调整服务器支持的人数,进行负载均衡。
负载均衡主要有两种策略:动态负载均衡和静态负载均衡。Big World引擎(网易《天下II》采用)采用动态负载均衡,它把游戏世界(无缝地图方式)分为多个区域,每个服务器承载其中的几个区域,当某个区域负载较高时,就将该区域细分为几个更小的区域,并把其中某些区域的数据迁移到其它服务器上,让其它服务器来承载。但是动态负载均衡对架构设计要求较高,还要考虑如何确保在数据转移过程中,玩家及其它相关对象状态的迁移、组队信息的更新等等能够保持正确,其中的工作量不小。
《大唐豪侠》采用静态负载均衡,我们把游戏世界分成多个场景,每个游戏服务器承载其中的几个场景,游戏场景的大小在设计时就已经确定下来了,不会在运营期间再进行动态调整。服务器的负载可以在游戏服务器启动前进行评估,评估时要考虑的因素主要包括地图是否是玩家特别喜欢的场景、地图是否经常被运营商选取进行线上活动以及过去的运营情况等。这样,在每周例行维护时,我们就能评估出每张地图的人数,从而估算出它们的负载,比较合理地把它们分配到不同的服务器上。虽然静态负载均衡比动态负载均衡缺少灵活性,但是实际效果也是相当不错的。
C++/脚本
采用即时战斗机制的游戏有一定程度的密集运算,因此,我们在开发过程中,一直在注意避免因运行效率导致设计目标无法达成的问题。计算量较大的部分主要是每帧对象的状态更新、寻路和战斗的效果计算,我们选择用C++实现这些功能让运行效率最大化,而其它非密集运算的功能,如普通的送信任务、找人任务等游戏玩法,则可用脚本方法来实现。 至于选择哪种脚本语言,则是仁者见仁、智者见智了,在开发《大唐豪侠》时,我们更熟悉Python,并且这门语言也有非常多的优点(除了速度慢了点以外),因此就选用了它。
界定何时该用脚本,何时该用C++有一个简单的标准,即改动不频繁,计算量大的应该用C++;改动频繁,计算量小的应该用脚本。使用C++,为了提高运行效率,会有较多的时间花在编译上;而脚本语言则牺牲了运行效率,获得了运行期间动态变化的能力。我们知道,游戏开发是一个不断响应快速变化的过程。如果频繁更新的部分也用C++来实现,则会显著的增加编译时间,开发的效率会很低。
虽然此处将这些因素分开列举,但实际上,这些问题并非独立存在,它们之间也会产生相互影响。例如多服务器架构是多进程间的协作,多进程的协作可自然延伸为多服务器架构。又如多场景地图和静态负载平衡都在一定程度上降低了开发的难度,多场景地图为服务器的静态负载平衡提供了条件。
设计要考虑的当然远不止上面谈到的这些内容,设计既是系统化的工作,也是一个寻求平衡的过程,这里只是站在宏观的层面,罗列了较为重要的部分,而这些部分就决定了游戏的架构最终会是什么样子。
游戏架构
基于上面的分析,服务器架构就变得很清楚了。
图01
简单的描述了架构中的服务器以及它们之间是如何进行交互的,虚线表示服务器间通过短连接交换信息,实线表示服务器间建立了长连接。为简单起见,这里只画出了2个GAS,实际上一共有5个GAS。
其中,GAC是游戏客户端(Game Client),其它是服务器,这些服务器的职责分别是:
GCC(Game Cluster Controler)
GCC是游戏服务集群(Cluster)的核心,是大脑,只有GCC才能决定集群级别信息的准确性和唯一性。
GCC肩负登录服务器的功能,玩家通过游戏客户端输入用户名和密码,随后用户名和密码被发送到GCC,由GCC转发给认证服务器,通过认证的玩家信息会通过DBS从数据库中取出,并在GCC上判断玩家应该连接的GAS,接着登录成功协议和重连协议被发送到客户端,客户端按照重连协议的指示连接到指定的GAS并进入指定的游戏场景。
GCC也是游戏世界中的位置寄存器,保存着所有在线玩家的位置信息。任何涉及跨GAS玩家的信息交换和事务处理,或者交给GCC处理,或者向GCC查询目标玩家的位置后由GAS直接与目标GAS协商。
DBS(Database Server)
DBS负责把游戏世界中的持久化数据存储到存储系统中,DBS把GAS/GCC与存储系统隔离开来,GAS和GCC完全不需要知道后台的存储系统是什么,数据是如何被存储的是由DBS来决定,它即可以存为文件,也可以存到数据库中。《大唐豪侠》的后台存储采用MySQL数据库。
鉴于DBS能直接访问后台的存储系统,非常重要和敏感,因此DBS只接收来自GAS和GCC的链接,并放在防火墙后面保护起来。DBS把GAS/GCC发来的协议转换为sql语句丢给MySQL服务器执行。DBS也会负责一部分的游戏逻辑,如离线玩家数据的处理;同时还会分析请求的特点,区分出请求能否并行执行,把能并行执行的并行处理,不能并行执行的就串行处理。
GAS(Game Server)
GAS接收客户端发来的协议,过滤非法数据包,并分析协议中的内容。例如分析玩家是要使用道具还是要使用技能,然后按照游戏玩法规则,根据玩家的行为完成游戏逻辑的计算。游戏的大部分玩法都在GAS上处理,只有少量的全局性逻辑或跨服务器的玩法会由GCC处理,离线玩法则主要由DBS处理。
在这些服务器中,GCC是大脑,所以如果GCC崩溃,游戏服务集群状态的一致性就无法保证,游戏就得立即停下来。而如果DBS崩溃,用户的数据无法读取,玩家就无法登录,也无法保存玩家数据,这样可能会导致玩家数据丢失,所以游戏也得立即停下来。相对而言,如果GAS崩溃,问题就不会有这么严重,主要只影响到该服务器上在线的玩家,我们可以重启GAS并将其重新加入集群中,GAS就能继续提供服务了。
为简单起见,我们没有设计恢复处理,而是选择了一种简单的处理方案:当GCC和DBS崩溃时,集群关闭;GAS崩溃时,自动重启重新加入集群。从运营效果看,这种方案不但能让我们快速发现问题,找到问题,避免了为实现容错而增加的复杂性,也避免了数据不一致性对游戏系统和玩家带来的损失。
总结
随着互联网的发展,越来越多的人享受网络生活,MMOG是其中最具吸引力的娱乐方式,玩家也不仅仅满足于小范围内的相互交流,他们期望能够与来自不同地域更多的玩家一起在游戏中探索、相遇、结识,因此,如何在一个游戏世界支持更多的玩家,就成为MMOG游戏开发的一个挑战和难题。分布式/集群是对该问题行之有效的解决方案,虽然在这种方案的实现过程中也会面临着许多挑战,但越来越多的游戏,根据自身的特点找到了合适的平衡点,它们的解决方案也一再地在实践中得到证实。本文站在《大唐豪侠》开发的角度,分析和探讨适合《大唐豪侠》的游戏框架,希望这种框架对其它游戏的设计也将有所启发。