为什么想要了解游戏服务器的发展历程呢?最近在研究和实现次世代的游戏服务器框架,熟话说“以史为鉴,可知兴替”,了解发展历程是为了知道每一次的跃迁都是为了解决什么样的瓶颈问题,这样我们才能更好地做出设计。
特级古董
开始的开始,我们都是孩子。可还记得(爸妈)孩童时的小霸王和街机?好吧,这时候大概还没有完整的游戏服务器的概念,联机全靠物理联机,插个手柄大家围坐一起打游戏。这些游戏也大多只存了一个排行榜、当前的关卡进度,数据直接刷写到硬件的ROM中,后面还诞生了专门用于存储的记忆卡。
差不多同时期的PC游戏也停留在单机时代,数据读写直接操作着硬盘上的文件。
史前巨鳄
随着游戏世界的发展,为了支撑更多的人一同游戏,出现了初代的游戏服务器。初代的游戏服务器可以是一台独立的服务器主机,有时也由某一台客户机充当。上世纪90年代,游戏服务器的C/S架构就有雏形了,事实上我们后期不断的升级拓展,本质还是一个大的C/S架构。这种游戏服务器和客户端没有明显分割的实现形式,在每个游戏运行端都有完整的代码实现,游戏逻辑在所有的运行端都有,数据还是由服务器端直接读写到文件中。局域网游戏在这个时期开始火热起来。
野蛮发展
随着游戏的继续发展,服务器的逻辑和客户端的逻辑都逐渐变得多了起来,C&S一体式的设计导致可执行程序过分膨胀,同时网络游戏也从局域网逐渐走向广域网,于是服务器和客户端开始分割,客户端只负责玩家侧的呈现,游戏的核心逻辑在服务器上执行。另外,直接对硬盘文件的读写在应对更多玩家参与游戏时也产生了许多问题,服务器宕机会造成文件损坏和丢失,进而导致玩家数据的丢失,磁盘文件作为游戏数据的持久化载体会造成文件碎片,在文件中检索速度慢,访问效率低,这一系列的问题促使游戏服务器引入数据库来对游戏数据进行读写。
数据读写的瓶颈解决了,可是随着游戏的继续发展,游戏的可玩性不断提升,游戏逻辑也变得越来越复杂,单台服务器逐渐承受不住游戏逻辑处理的压力。于是,我们的前辈们开始划分游戏逻辑,将服务器上的游戏逻辑处理分成较为独立的多份,由不同的主机处理。游戏逻辑处理的拆分由具体的游戏业务和服务器架构师决定,比如可以将主城区聊天交易的逻辑和郊外打怪的逻辑分离,也可以将游戏城市分为多个区,按区分离,跨区移动需要类似“传送”的道具,然后客户端切换游戏逻辑服务器重新加载等等。
逻辑拆完之后,游戏继续发展,游戏逻辑继续变复杂。这时候,多台处理不同游戏逻辑的服务器多数时候需要同一个玩家的信息,但是由于在不同的游戏服务器上,它们没有办法互通数据,它们只能通过数据库去获取需要的数据,而这些需要的数据,很可能另一台游戏逻辑服务器刚向数据库要过。多台游戏逻辑服务器同时向数据库的存取操作使得数据读写又出现了瓶颈。为了解决这个问题,前辈们在数据库前面加入了一个数据库代理节点,游戏逻辑服务器们向代理节点请求读写数据,代理节点做了内存级别的cache,读写的数据会先暂存在cache中,在合适的时候再交换到数据库中。这样,如果一个玩家的数据被一台游戏逻辑服务器先访问了,玩家数据就会保留在代理节点的内存缓冲中,另一台游戏逻辑服务器在访问的时候就会直接从代理节点的内存缓冲中获取。
到当前为止,我们的客户端还是和不同的游戏逻辑服务器直接进行交互的,随着处理逻辑的复杂化和服务器数量的增多,由客户端直接连接到各个游戏逻辑服务器的方式很难维护整体架构的稳定性和扩展性。当游戏更新需要加入新的逻辑,引入一台新的游戏逻辑服务器时,需要改动不少客户端的代码,并至少要求客户端的应用程序要完全更新。要解决这个问题,需要分离玩家的接入过程,在客户端和游戏逻辑服务器间加入一个网关节点,网关节点负责导流,玩家客户端先连接到网关服务器,网关服务器再根据具体的请求访问不同的游戏逻辑服务器,不同游戏逻辑服务器间也可以通过网关服务器进行交互。
我们现在回过头来看看,在每一次服务器框架遇到瓶颈时,前辈们都干了些啥?
- 拆:一台不够就多台
- 加:软件工程中遇到的任何问题都可以通过增加一个中间层来解决
简直太暴力了!但都解决了工程问题。
基于这个模式,大型的MMO游戏的服务器框架一般长成这样:
每个地图由一个World游戏逻辑服务器负责,玩家的装备管理、聊天、交易等等分别由Manage、Chat、Exchange等等游戏逻辑服务器负责。然后由一堆的数据库代理节点用来缓存数据库中的数据,适时地交换数据库与缓存中的数据,代理节点也可能同时缓存着多个库中的数据。玩家客户端连接的是一系列的网关服务器,网关服务器负责转发客户端与各个服务器上的交互流量。
无缝地图服务器
大约从魔兽世界开始,出现了无缝地图服务器。emm…但是国内似乎还没有见到有类似的服务器实现,切地图基本还是采用重新读条的方式(影响沉浸式体验)。无缝地图服务器相比普通的MMO,除了相同的一些服务器节点外,至少还需要有一个NodeMaster做地图的分割和负载均衡,将大地图分割到一系列的地图Node上,以及一个NodeRouting负责寻路,处理玩家在多个地图Node的公共管理区时的问题。
无缝地图服务器需要处理的一个棘手问题是,玩家在划分的一个地图块的边界走动时的平滑切换的问题。常规的一种做法是在地图边界划定一定范围的公共管理区,公共管理区的范围一般由玩家的视线范围决定。当玩家处在公共管理区时,将会与公共管理区交叠的所有Node服务器进行交互,由所处的位置的Node服务器负责主要的管理和计算,客户端会从多个Node服务器中获取玩家周围的地形等数据。
假设玩家的路径为1-2-3-4-5,在位置1时由Node 1管理,只加载Node 1上的地图数据,当玩家到位置2的时候,进入了公共管理区,玩家还是由Node 1管理,但是地图数据会从Node 1和Node 2中来,当玩家越过边界即位置3,到达位置4时,管理权将移交到Node 2,但地图数据还是从Node1和Node 2中来,玩家到位置5时,离开了公共管理区,只加载Node 2上的地图数据。假设玩家的路径是下面那条a-b-c-d-e,玩家在位置c、位置d时,将处在4个Node的公共管理区,地图数据将从4个节点中来。
有的无缝地图服务器还会加入动态划分地图的算法,在玩家密集的地方拆分地图。当然,大多数时候可以通过业务需求提前知道并划分好地图,例如,玩家非常多的主城区的地图会划分很多块由多个Node承载,而玩家不太集中的郊外地区,Node所管理的地图范围就会大些,另外,地图边界可以做在河道、桥上等等,这样玩家不会在公共管理区待太久。
总之,大地图的分割加载与边界处理是无缝地图游戏服务器的技术关键点,各个地图节点服务器需要相互协作服务公共管理区内的玩家,客户端需要分级加载地图数据(也许还可以用上虚拟纹理技术?另一个小伙伴最近在研究这玩意儿)。另外,除去关键技术不谈(其实大地形的论文不少,实现的DEMO演示也不少,但是从学术界到工程落地还是有一段不短的路的),工程难度够让团队喝上一大壶了,大地形的地图制作,整个场景的建筑、植被、水域、地形,还有各种NPC角色和动物,复杂的游戏逻辑与交互…搞个氪金游戏它不香么。
看个案例
目前的游戏服务器架构基本都是上面几种形式的衍生,比如一个大型多人在线的副本游戏实现成下面的形式。玩家通过客户端首先连接到游戏的网关服务器上,网关服务器从数据服务器上获取玩家的数据,如果玩家需要交易等等则交由相应的服务器上进行。玩家开始一局游戏时,会交由Matching服务器做匹配,所有玩家匹配成功后,移交到Rooms服务器上进行游戏逻辑计算,负载均衡器处理各个服务器节点的负载均衡。一局游戏结束后,又从Rooms中退出,玩家接着交易或者开始新的一局匹配。
次世代游戏服务器
游戏服务器发展到现代,基本是满足了当下的需求。但是也存在着一些困境(升华的追求吧):
- 可扩展性差,每个服务器都承担着独立的大块功能,无法按照业务逻辑做细粒度的灵活划分。(比如之前交易和武器升级在一台独立服务器上处理,随着玩家数目增多,一台服务器撑不住了,如果要拆开交易和武器升级的逻辑放在两台服务器上,需要改动不少代码)
- 可调试性差,多机部署,多进程协同的产品,缺少自由拆分和组合的能力,开发阶段难以协同调试分布于多个服务器上的代码。(开发调试能采用AllInOne,提升开发效率)
So,我认为次世代游戏服务器在实现当前的游戏服务器基础能力外,还需要具备这三个特点:组件式、可拆分、易伸缩。
我们真的需要吗
看着游戏服务器一步一步发展成今天这样庞大的、各个模块高度聚合、模块间独立解耦的架构,作为在半个游戏界燃烧生命的新一代搬砖人来说,还挺希望一上来就写一个无敌牛逼最强最先进的服务器框架。但是,如果要作为工程化的交付件,我们要回答一个问题“这么庞大复杂的系统真的是我们需要的吗”,作为工程师,更应该想的是我们写的东西是否符合真正的业务需求,当下的人力是否能够支撑得起,交付件什么时候转测,投资回报率有多少。我们可以实现一个符合当前需求的基础版本,再在基础版本上不断迭代,一边适应新的需求,一边优化架构。合适的才是最好的。
协议
本文以上内容遵循CC BY-ND 4.0协议,署名-禁止演绎。
转载请注明出处:https://tis.ac.cn/blog/kongdeyou/game_server_history/
作者:kongdeyou(https://tis.ac.cn/blog/author/kongdeyou/)