前言

本文是对分布式服务器架构的由来进行介绍,要开发一款网络游戏自然不能对开发网络游戏的历史一无所知,充分了解网络游戏开发的历史也能加深对当前网络游戏架构的理解

下面就来看一看网络游戏的服务器是如何一步步的从一个简单的服务器逐渐过渡到当前热门的分布式架构服务器的

版权声明

  • 本文为“优梦创客”原创文章,您可以自由转载,但必须加入完整的版权声明
  • 更多学习资源通过私信我获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)
  • 点赞、关注、分享可免费获得配套学习资源
  • 点击观看完整视频

卡牌、跑酷等弱交互服务器

  • 最简单的网络游戏就是弱链接网络游戏,类似于天天酷跑和单机卡牌这样的只需要在每局游戏完成以后把数据上传到服务器的游戏就可以被称为弱链接网络游戏
  • 这种游戏往往会使用更简单的弱链接游戏服务器,也就是HTTP服务器,因为这种游戏的交互比较弱,玩家与玩家之间不需要实时面对面PK,所以根本不需要专用的游戏服务器
  • 这种游戏在登陆时会采用非对称加密方式来对玩家的密码进行加密,服务器会根据客户端的用户ID号、当前时间戳、以及服务器端的私钥计算出一个哈希值,以得到加密后的密钥,然后把密钥发送给客户端之后,双方就可以用通过加密的密钥来进行通信,这是最简单、最早期的服务器采用的加密方式,这种加密方式的特点就是安全性高,但速度慢

第一代游戏服务器

  • 世界上第一个网络游戏:MUD1
    • 世界上的第一个网络游戏叫做MUD1,它是文字类的网络游戏,类似于现在玩的文字冒险游戏
    • MUD1是由1978年英国某著名财经学校的学生编写的,并在1980年时将MUD1接入到了一个类似于现在的internet的网络里
  • 网络游戏的鼻祖:MudOS
    • 由于MUD1的游戏源代码是共享的,所以在之后出现了许多改编版本,MUD1也在全世界广泛流行起来,并以MUD1为基础产生了一个开源游戏框架:MudOS,它是众多网络游戏的鼻祖
    • 因为玩家与玩家之间有比较强的交互,所以MudOS使用了一种单线程的无阻塞套接字来服务所有玩家,单线程的好处就是不容易出现多线程的死锁、数据不同步问题,但是单线程的效率要低一些
    • 并且由于MudOS是采用无阻塞套接字,所以它的网络数据收发比较流畅,可以服务多个玩家,如果使用阻塞套接字就只能一次服务一个玩家
    • MudOS里的所有玩家请求都是发送到同一个线程中处理的,主线程每隔一秒钟就会对玩家对象进行一次更新,更新内容包括网络的数据收发更新、对象的状态机、处理超时、刷新地图、刷新NPC等等,这里就已经看出MudOS已经有了类似于当前网络游戏的感觉了
  • MudOS的特点
    • 最早的游戏是采用房间的形式来对游戏世界进行管理的,每个房间都有东、南、西、北四个方向,可以进行移动,移动时会进入下一个房间,由于欧美游戏最早的网游都是采用迷宫的形式,所以场景里最基本的移动单位被称为房间
    • MudOS使用了一门独创的编程语言,叫做LPC
      • MudOS用LPC脚本语言来描述游戏世界、游戏中房间和房间之间的联系、NPC、还有各种各样的剧情,游戏里的高级玩家可以不断的通过修改脚本,来为这个游戏添加房间、增加剧情
      • 早期的MUD1上线时只有17个房间,开发MUD1游戏的这位同学毕业后把项目交给了他的师弟,他的师弟在之后为游戏添加了各种各样的新玩法和100多个房间,最终使MUD游戏发扬光大
    • MudOS的游戏用户是使用类似于Telnet的网络客户端,通过TCP协议链接到MudOS,然后使用纯文字来进行游戏,每一条指令都使用回车键进行分割,也就是当你输入完一个指令,按下了回车,那么这个指令就会发送到服务器上,并做相应的处理,这跟现在的网络游戏没有太大的差别
    • 在那个年代游戏的数据是直接保存在服务器的文件里,这是一种非常简陋的方式,因为访问文件的效率没有访问数据库快,并且当时的游戏服务器能够承载的人数是4000人左右,游戏内容通过LPC脚本进行定制
  • MudOS为国内的第一代MMORPG游戏提供了稳固的支持,一直持续到2003年,后面对基于MudOS的游戏增加了很多图形化的东西,但这些非图形化的内容一直都是MMORPG后端的本质

第二代游戏服务器

  • 在2000年以后,网络游戏脱离了最初的文字形式,进入全面的图形化阶段,在这个过程中最先承受不住就是服务器,最先出现性能瓶颈的地方是文件访问,第一代的MudOS服务器里的数据都是以文字形式进行保存的,随着用户逐渐增多,如果要为每一个用户建立一个数据文件,那么就会有很多的用户文件
  • 用户频繁的上下线会使服务器也频繁的对用户数据进行读写,从而导致服务器的负载越来越大,难以承受,而且最早期的磁盘分区采用的格式是EXT格式,比较脆弱,只要稍微一断电就可能发生玩家数据文件损坏的问题,很容易造成数据丢失
  • 要解决这些问题就需要拆分文件里的数据,把它放到数据库里,如果每个玩家都要有一个文件,那么由于文件的碎片存在,磁盘对这些数据文件进行访问的效率会非常低,所以需要引入到数据库中,数据库经过了很多数据访问的优化,所以不会产生这种碎片文件

  • 上图是第二代服务器的第二种形态,随着游戏服务器的演化发展,游戏服务器已经脱离了陈旧的基于MudOS的体系,各个游戏公司在参考了MudOS组织结构的情况下,开始自己用C语言重新开发游戏服务器,并且游戏脚本也已经抛弃了传统的LPC脚本,开始采用扩展性更好、性能更好的Python或者Lua来替代

  • LPC脚本的主逻辑使用的是单线程,随着游戏内容的增加,传统的单线程单服务器逐渐成为了瓶颈,所以就有人将游戏服务器拆开,使用多个服务器来提升游戏性能

  • 上图是第一代服务器的第三种形态,游戏服务器经过第二种形态的改进后,逻辑处理效率已经提高、逻辑处理的压力也被缓解了,但两台游戏服务器同时访问数据库造成的大量重复数据访问、数据交换,使数据库访问又成了一个瓶颈

  • 为了解决这个问题开发者为数据库访问再增加了一层代理层

    • 数据库代理层的作用是不让游戏服务器直接访问数据库,要访问数据库就必须先访问游戏代理
    • 比如服务器要从数据库里拿出数据,取出的数据会被放到代理层的服务器上,这时代理层的服务器里就包含了早先访问数据库时拿到的数据
    • 当有其他玩家又要访问数据库获取相同的数据时,代理层就可以直接把缓存的数据返回给第二个玩家,这就是数据库的前端代理
  • 游戏服务器不再直接访问数据库,而是访问游戏的代理,然后再由代理去访问数据库,同时在内存中提供内存级别的缓存,在MySQL4之前它没有提供存储过程,所以前端代理一般是跟MySQL服务器跑在同一台机器上面的,它会把服务器发过来的数据访问指令进行转换,拆分成具体的数据库操作,在一定过程上替代了存储过程

  • 第三型的游戏服务器结构并没有持续太长时间,因为游戏玩家在切换场景时经常要切换连接,导致中间状态容易混乱
    • 比如玩家在第一次登陆游戏时进入了服务器A,第二次又进到了服务器B,这时由于游戏的服务器太多,服务器在管理时就容易出现混乱
  • 数据之间的交互也很麻烦,所以要继续对服务器进行拆分,第四型服务器拆分出了一个网关服务器,网关服务器就像网络设备里的硬件网关一样,它是提供给外部所有的玩家访问游戏服务器的关口
  • 网关服务器把玩家访问服务器的功能全部提取出来,让玩家统一连接网关服务器,然后再由网关服务器把功能数据转发到后端的游戏服务器,而服务器与服务器之间的数据交换也是统一经过网关来进行交换
  • 举个例子:如果游戏世界A要与游戏世界B进行数据交换,那么我们能不能直接交换呢?
    • 在第二代的第四型服务器里是不可以进行直接交换的,需要先把游戏世界A的数据通过网关转发到游戏世界B,然后由游戏世界B把数据读出来后,再通过网关把它转发给游戏世界A
    • 看上去数据的交换复杂了一点,但它保证了架构的稳定性,因为软件设计有一个很重要的原则:高内聚低耦合
  • 这种类型的服务器能够稳定的为玩家提供游戏服务,一台网关服务器能够支持一到两万人

  • 随着年代的发展,网络游戏玩家越来越多,玩法也越来越多,传统的服务器性能又不能够满足需求了,这时就需要采用服务器扩展的方式来满足服务器的性能需求了
    • 按照前人的经验,把MudOS的各个服务拆开会使它的性能更好,所以既然网关服务器可以拆分出来,那么基础服务也可以进行拆分,比如将基础服务中的聊天、交易服务拆分到聊天服务器和交易服务器上,让一台服务器只负责某一项特定的功能
    • 同时也增加一些关键服务器的数量,比如上图中的逻辑服务器(GAME World)和网关服务器(GATE)
  • 这样的服务器有什么好处呢?
    • 这样的模型是一个比较成功的模型,很多成功的游戏都运用了类似的架构,并且发挥了它的性能优势,但这样的服务器又有两个挑战,就是每增加一级服务器,状态机的复杂度可能就会翻倍,比如每一级有十种状态,再增加一级,就是十乘以十,也就是有100种状态
    • 状态机复杂度翻倍会导致研发和找bug的成本无限上升,并且对于开发组的挑战也比较大,一旦项目时间比较紧张,开发人手不足时,服务器很可能会出现停机的情况

第三代游戏服务器

  • 第三代的游戏服务器的普及是从2007年开始的,也可以说是从《魔兽世界》开始,那时无缝的游戏世界地图已经深入人心了,在之前的游戏里玩家走几步就要切换场景,每次切换场景都要加载几十秒,这是非常破坏游戏体验的,所以从2005年以后的大型的MMO开始,无缝地图就成为了标配
  • 以前玩的《暗黑破坏神》是在你进入到某一层时加载一个地图,到了第三代游戏服务器后就不是这样了,第三代游戏服务器的架构如上方图片所示,也是有两个网关服务器,玩家是连接网关服务器的,不是直接连接后端服务器
  • 由于第三代服务器没有地图的存在,不能像第二代服务器一样有游戏世界A或者游戏世界B,所以出现了节点服务器,一个节点服务器可以认为是游戏里的一块区域,如果游戏里的无缝地图分成10块或者是20块,那么就会有20或者10个节点服务器,在节点服务器中要进行寻路时只要访问地图服务器就可以了,游戏服务器只是提供了地图寻路的服务

  • 无缝地图服务器中最关键的点就是地图的节点管理,一个节点就是一块区域,当玩家从节点A移动到节点B时发生了什么样的事情呢?
    • 玩家在上图中使用绿色的三角形表示,
    • 当玩家从节点A走到节点B需要做如下处理
      • 玩家1是完全由节点A控制的,玩家3是由节点B控制的,处于两个节点边缘的玩家2同时由节点A和节点B来进行管理,因为玩家在地图边界时可能会来回走动,直接将他交给节点A或节点B进行管理都不太合适,所以就交给节点A和节点B共同管理
      • 这块被节点A与节点B共同管理的区域叫做共管区,玩家2从节点A移到节点B的过程中,会同时向节点A和节点B发送请求来获取节点A和节点B的情况,玩家2到达节点A的地图边界时就会开始请求节点B的一些数据了,防止在跨域节点时出现卡顿
      • 直到玩家2彻底离开节点A与节点B的共管区,玩家2才会彻底离开节点A,来到节点B,这个过程中一般会使用链表或者动态数组来进行管理
  • 把游戏世界的地图分割为一个个的区块交由不同节点来管理,但这些节点在地理上是没有必要互相链接的,比如在平坦的大陆周围有一些高山,高山附近的玩家比较少,那么就可以把这些高山区块统一交给一个节点来管理,节点只是逻辑上的概念,所以这个管理所有的高山区的节点没有必要跟平坦大陆的节点连在一起
  • 节点所管理的区块可以在游戏运行时根据游戏的负载实时调整,负责进行调整就是节点管理服务器(NodeMaster),它是非常重要的一个服务器
  • 节点管理服务器负责管理节点的负载均衡,在开发节点服务器时,我们碰到的第一个问题就是节点服务器需要和玩家进行通信
    • 以前使用按照场景切割服务器的方式问题不大,只需要访问一下网关服务器就可以把数据缓存起来,但现在服务器种类增加了,玩家又不停的在节点之间来回移动,如果按照用户编号来进行查询就比较麻烦
    • 另外一方面,网关服务器要根据坐标来计算玩家在哪个节点上,玩家来回移动也会导致逻辑越来越麻烦
  • 为了解决这个问题,第三代服务器特地增加了一个对象服务器,原来的方式是网关服务器根据一定的算法计算玩家应该在哪个节点上,由于玩家可能会从节点A跑到节点B去,所以就需要在节点B上面重新加载玩家放在节点A上面的数据,再把它挪到节点B上面
  • 并且如果一个玩家与另外一个节点的玩家进行交互,那么中间的过程就会非常复杂
    • 首先必须要通过网关查找来与节点B的玩家进行交互,节点B的数据又要通过网关再转发给节点A,过程非常繁琐
  • 所以第三代服务器特地增加了一个对象管理服务器,对象管理服务器专门管理一个节点上的所有对象,一个对象可以是在节点A,也可以在节点B,还可以在节点C,这样就变成了节点只管理一个区块,对象的数据放在对象服务器,区块的数据放在节点服务器

  • 上图是第三代服务器的第二种形态,网关服务器经过精简后就没有那么多复杂的功能了,只是负责数据转发,用户逻辑放在对象服务器里进行管理,服务器经过网关找对象时通过一个很简单的算法就能知道对象应该在哪个对象服务器上,因为所有的对象都是被一个对象管理器管理的

动态负载均衡

  • 第三代的服务器上有的节点被玩家访问的次数比较多,比如如果一个游戏服务器里的交易NPC全部都集中在一块区域里,玩家会经常访问这块区域,那么这块区域的负载就会比较大
  • 要解决这个问题可以将人群比较拥挤的区块分割的小一点,让一个节点管理一个小区块,这个区块里面要管理的NPC和玩家比较多,同样的也可以将平时没有什么玩家走动的打怪地图划分的大一些,这就叫做动态负载均衡
  • 动态负载均衡是由节点管理服务器负责管理的

战网游戏服务器

  • 战网服务器也可以叫做第三代第三型服务器,传统的服务器都是基于最早的MudOS架构不断进行延伸,这种架构对于一些实时性的战斗交互处理效率太低了,所以它需要一种战网服务器
  • 战网服务器的特点就是处理效率比较高,很多计算都是放在客户端进行,服务器只负责数据转发,《王者荣耀》就可以认为是服务器转发的战网服务器,但它不是一个纯粹的战网服务器,它是类似于服务器转发的战网服务器,经典的战网服务器跟MMORPG游戏服务器有两个区别
    • MMORPG游戏服务器是分区分服的,北京区的用户和广州区的用户老死不相往来
    • 战网服务器每局游戏一般都是开房间,八个人或者十个人一起参加游戏,全国用户都可以访问同一个服务器,所有玩家都可以一起进行游戏,玩家与玩家之间使用的是P2P方式进行连接的
    • 以前的战网服务器只负责把玩家连接在一起,然后从所有玩家中选出一名玩家作为主机,现在为了游戏的公平性,服务器都已经不采用这种架构了,而是让所有数据都经过服务器进行转发,这样就保证了客户端玩家不太可能作弊,这种架构比这种传统的战网服务器要安全一些,同时又不像MMORPG游戏那样所有东西都在服务器上计算
  • 战网服务器都包括哪些东西呢?
    • MatchMaking
      • MatchMaking服务器是用来专门做玩家的匹配服务的,它是通过创建房间、加入房间、自动匹配、玩家邀请等等方式来组成游戏房间的
    • STUN(牵引服务器)
      • 牵引服务器的目的就是把其他玩家连接到战网服务器当前的主机上面,在某些情况下,比如电信和网通的用户连接比较慢时,战网服务器就会设置一个转发服务器,来转发无法联通的玩家之间的操作
      • 其实王者荣耀也是采用这样的方式,所有的数据都要经过服务器转发一遍

第三代服务器的困境

  • 比较接近现代游戏服务器的第三代游戏服务器已经讲完了,那么这种现代的游戏服务器它又存在哪些困境呢?
    • 1,服务器的可扩展性比较差
      • 每一个服务器都承担了独立的功能,没办法按照业务逻辑灵活划分服务器
    • 2,可调式性差
      • 现在的服务器有两种形式,单线程多进程和单进程多线程
      • 多线程服务器的线程与线程之间可能会引发死锁、数据不一致的问题,死锁问题简单来说就是两个玩家同时购买一个唯一道具导致两个玩家都无法购买道具的问题,详细内容可以参考我们的vip课程
      • 单线程多进程的形式也有一些问题,由于客户端和服务器都是采用脚本语言进行逻辑开发的(比如Lua、Python),而这些脚本语言在进行调试时只能通过打印日志来看到程序问题,所以在出现问题时想要进行调试就会比较困难
    • 3,不能跨平台
      • 游戏服务器最常用的就是Linux和Windows服务器,而第三代服务器不能跨Linux和Windos平台,而且第三代服务器的客户端和服务端使用的并不是相同的语言,也就是使用第三代服务器开发游戏可能需要学习两种以上的编程语言
    • 4,代码繁琐
      • 第三代服务器使用的C语言没有异步这种语法结构,也没有Lambda表达式、想要使用正则表达式还需要导入第三方库
      • 第三代服务器的模块化程度也不够,不是基于组件式进行开发的
    • 5、无法做到不停服更新
      • 如果要做不停服更新的话服务器必须要支持脚本语言,如ToLua、XLua
    • 6、代码可重用性差,没有Actor支持
      • 很多时候客户端跟服务器的功能是相似的,但在第三代服务器中你需要写两份代码功能相似的代码,所以很难维护
      • 没有Actor支持,就意味着无法简洁的支持大规模并发
    • 7,数据库架构繁琐
      • 过去的服务器架构用的比较多的就是使用Redis做数据缓存,用MySQL做数据库,但实际上我们只需要用一种数据库就可以了,这种数据库叫做Mongo
    • 8,缺乏简单、高效的网络协议支持
      • 现在的服务器开发里都会用ProtoBuffer,所以这个问题已经在第四代网络游戏服务器里已经解决了
    • 9,不支持帧同步
      • 因为过去大部分服务器架构都是针对MMO游戏开发的,所以对帧同步支持有限,而且实现帧同步必须要有可靠UDP协议
        • TCP协议是比较安全的网络通信方式,它不会出现数据丢包、数据包时序混乱等等问题,但TCP数据包为了保证可靠性与安全性,牺牲了一些数据包收发的效率
        • UDP恰恰相反,它是一种不可靠的数据包传递方式,它会出现数据丢失、时序混乱等问题,但它的传递的速度很快
        • 第四代游戏服务器会用到UDP协议,为了保证可靠性我们需要实现一个可靠UDP协议,它不会出现数据丢失、时序混乱等问题
      • Unity的数学和物理运算库是有问题的,它会出现浮点数运算误差,如果你在C#中用一个非常大的浮点数加上一个非常小的浮点数,在你把结果打印到日志上时就会发现算出来的数值跟你想象的那个不一样

写在最后

  • 更多学习资源通过私信我获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)
  • 点赞、关注、分享可免费获得配套学习资源
  • 点击观看完整视频