游戏服务器框架归纳分析

这篇blog题目涉及的范围真大!以致于在这里须要先写一篇前言把范围缩小。选择写这样一个系列的文章,主要是想给工做了两年的本身一个交代,或者说是一个阶段性的总结。两年时间里,房价依然再涨,工资依然跑不赢CPI,某人依然在仰望星空。期间不少梦碎了,不少还在坚持着,生活过得波澜不惊。而我也从刚毕业是的青涩逐步蜕变为“老油条”。不知道是一种悲哀、仍是一种悲哀、仍是一种悲哀....... 庆幸的是梦还在继续,一颗倔强的心还在坚持。但愿明天的明天被束缚的心能回到梦开始的地方!html

 

==========================我只是条分割线========================前端

    做为本系列blog的开篇前言,本文主要明确网络游戏服务器构架的设计目标,并做出一些限定。由于本系列所讨论的服务器端构架只适用于部分网游,并非一个通用的网游服务器构架。java

 

设计目标:mysql

  1. 支持的游戏类型:大型MMORPG游戏,相似魔兽世界(有大世界,不是开房间式)。
  2. 链接方式:以TCP长链接为主。动做类游戏并不在本文讨论范围内(由于本人并无参与开发过动做类游戏),若是有时间能够研究一下龙之谷(部分使用UDP传输)、天下贰(所有使用UDP传输),相似的逆向工程网上已经有人作了。
  3. 在线人数:保证最大1w人左右在线还能比较流畅的运行。若是在线人数大于1w对客户端的同窗和策划的同窗都是很大的挑战。
  4. 服务器能够以多进程的形式布置在不一样的物理主机上,也能够布置在同一主机上,考虑效率的同时兼顾可扩展性。
  5. 能在普通配置的服务器上流程运行,物理主机配置:按照DELL 1950、DELL R610上32G内存的标准来部署主机,通常公司是用不起WOW的小霸王的.......
  6. 内存不用过多的考虑,由于如今服务器的内存已经很大了。减小内存使用会放到模块设计、详细设计里。不在构架分析的讨论范围内。
  7. 偏格斗的游戏会对CPU和带宽要求比较高,设计时须要进行讨论。

 

    设计目标就这些多说无益,核心就是设计出可以支持相似魔兽世界的大型MMORPG游戏的网游服务器端。具体的设计以及设计时的取舍、须要解决的问题等,会在后续的文章进行详细的介绍。程序员

 

 

网络游戏服务器构架设计(二):刀剑Online - 链接负载服务器CLS

 

 

 

    本文并无涉及什么逆向工程,只是拜读刀剑Online服务器端主程的文章后[1],想结合本身的经验谈一谈。web

PS:因为题目范围太大,本系列的前言作了一些限制。算法

 

1、网络游戏服务器sql

    要想设计好网络游戏服务器的构架,首先须要知道网络游戏服务器在玩家游戏过程当中发挥什么做用。就我我的的理解:网游服务器在玩家游戏过程当中扮演上帝的角色。玩家在服务器制定的规则下进行游戏,服务器负责同步在线玩家之间的属性、操做、状态等等,最终在多个不一样的客户端呈现一个“统一”的游戏世界。数据库

    所谓的服务器构架在本系列blog中,主要是指如何将服务器各部分合理的安排,以实现最初的功能需求。好的结构不是一蹴而就的,是经过需求的推进一步步的完善。并且每一个设计者心中的标准不尽相同,因此我认为并无绝对优秀服务器构架。本系列文章中所谓的优秀构架是指各方面达到一种平衡(包括成本等的非技术因素)。编程

    下面先介绍刀剑Online的服务器构架(后续还可能有WOW、天龙等):

 

2、刀剑Online

image    

图1 刀剑Online服务器构架

 

    看了像素的技术总监魏华的文章[1],感受有点意思。文章中所介绍的服务器构架并不复杂,但知足通常MMORPG网游要求应该是绰绰有余了。按照魏华本身的话,这样的服务器构架主要知足可以接受如下几条限制的网络游戏:

  1. 游戏同时在线人数在1w人如下。
  2. 服务器为多进程程序,可部署在一台或者多台机器上。
  3. 服务器内存足够大,通常一个进程1~2G的内存需求仍是应该知足的,64位系统能支持更大的内存需求。内存这块主要看游戏的设计需求。
  4. 刀剑属于格斗性质的网游,对CPU和带宽有必定的要求。(这篇文章写于2005年,当时刀剑多是按照256k、512k或1M ADSL的网速进行设计的。如今已经开始普及10M网,带宽和网速应该能够知足),网络延迟的问题本文后面再作展开。对CPU的要求主要影响可承载人数。
  5. 地图采用独立小场景的管理模式,各个场景之间经过传送点来链接。每一个场景服务器程序分管一部分地图。

    服务器包括游戏中和游戏外两大部分,这里主要讨论游戏中的服务器构架,相似客户端自动更新等的游戏外服务器不在本文的讨论范围。

    接下来对刀剑Online的服务器构架的各个部分进行详细的分析,其中包含不少我本身的想法,不少内容都是我猜想的,所以不能“信觉得真”。

 

2.1 链接负载服务器(Connection Load Server,CLS)

    游戏客户端在游戏过程当中其实是和链接负载服务器(简称CLS)进行链接并作数据交互的。如文中[1]所诉,CLS主要的做用是:

  • 把网络链接和真正的游戏逻辑隔离开,下降游戏逻辑服务器处理网络交互的负担,同时提升游戏的安全性。有了CLS,刀剑Online的服务器对于玩家来讲就是一个黑盒,以下图:

image图2

  • 使场景服务器(Zone Server)更为独立——客户端链接CLS而不是直接链接Zone Server的好处是:用户切换场景服务器时,并不会致使原来的TCP链接断开,从而使设计更为简单和独立。
  • 提升发送广播消息的效率,好比须要全服广播时,游戏逻辑服务器只须要对CLS发送一条广播指令,而向每一个用户的广播工做由CLS完成。
  • 完成客户端数据交互的加密解密过程。

链接服务器的主要工做正如上述魏华谈到的,但有几点并无作出强调,下面本人结合平时的实际工做给出一些补充(不必定正确):

根据经验:

    CLS做为与client创建链接、进行数据交互的“Gate”,从程序角度来看CLS的代码应该是最简洁高效的。由于CLS主要负责与客户端交互数据是的加密解密、以及数据搬运。而使用流加密算法RC4对整个数据流进行加密解密是很耗CPU的,所以代码的高效在这个模块是十分的重要。

    为了提升程序的运行效率,CLS程序每每会使用-O3的选项进行编译,这无形中又对代码的编写有更高的要求。CLS通常会有本身的打包机制(控制发送频率),所以常会使用TCP_NODELAY选项禁用Nagle算法。

    CLS常会被分配到不一样的物理机器上,由于操做系统在处理TCP包时,须要经过软中断来通知进程或者唤醒system call,在服务器十分繁忙的时候CPU可能处理不过来。解决办法是:使用多核的服务器,而后把TCP的软中断平均分配到多个CPU。(一些操做系统默认只使用0号CPU来处理,Fedora Core release 2默认就是只使用0号CPU,较新的版本我没有作研究)

    CLS在作数据流的解密后,每每须要把数据包构形成内部服务器进程间通信使用的protocol,这种protocol模块要独立,序列化和反序列化的接口要稳定,这样之后须要更换协议模块也不至于伤筋动骨。可使用像google的protobuf这样的开源协议,减小开发难度。

    CLS负责创建和client的链接,多会使用多个CLS进程才能支撑1w的在线人数,所以在CLS前端通常会有负载均衡的程序,负责把创建链接的请求均匀的提交到各个CLS。

   有一个须要讨论的问题:做为服务器端的“Gate”,只负责数据转发的CLS是否须要对client发过来的数据进行彻底的解密?或者只解密包头,知道转发的目的地便可?(RC4并无增长流的长度,所以能够只作部分解密)

  CLS只作部分解密:

好处:将耗费CPU资源的解密功能分摊到别的进程;各进程各服务能够在解密后用不一样的方案来构造本身的protocol。

  CLS作彻底解密:

好处:能够提早过滤部分无效的消息,只作部分解密也能够作提早过滤,可是这样太过于依赖协议的设计;在CLS处作彻底解密,则日后服务器端的之间的消息传递都是明文,利于抓包查错;因为CLS的功能比较简单,很容易经过加机器来进行扩展,所以计算放在CLS上是比较明智的选择。

 

总结:

    CLS是一个功能相对简单但要求代码简洁高效的程序,在设计实现的时应该注重效率及代码编写规范。通过对比在CLS程序对数据流进行彻底解密是利大于弊的,推荐使用这种方案。

 

 

网络游戏服务器构架设计(三):刀剑Online - 总控服务器、场景服务器

 

 

 

    上一篇《网络游戏服务器构架设计(二)》介绍了刀剑Online的链接负载服务器CLS,博友提出质疑“说得不够详细,好比你怎么,场景服务器怎么才算一个场景服务器,场景服务器切换怎么处理不断线后链接另外一个场景的,还有不少细节问题没有说到”,本篇就来介绍游戏服务器最为核心的部分:游戏逻辑服务器,同时也回答了这位博友的问题。

PS:本篇的文章结构主要分两个部分,前半部分(2.2节)介绍刀剑Online如何实现游戏逻辑服务器,后半部分(2.3节)为本人结合实际工做对这套服务器构架作出的一些展开解释及补充,主要对设计思想进行分析。精彩在后面哦!

 

-------------------------------------------我只是条分割线--------------------------------------------

 

先来回顾一下刀剑Online的整体构架图:

 

 

 

2.2 游戏逻辑服务器

    顾名思义,就是和游戏具体逻辑相关的服务器(这应是一个统称)。这块是网游服务器端的核心部分,不一样的游戏差异会很大。在刀剑中,游戏逻辑服务器分为两部分:总控服务器和场景服务器。

 

 

2.2.1 总控服务器(Master Server,MS)

    关于总控服务器的做用,刀剑Online的主程是这么解释的:

    总控服务器(如下简称MS)的做用之一是负责玩家在具体游戏内容以外的操做(即.玩家进入场景服务器以前地操做)。如:登陆、注销、各类角色操做(建立、删除、选择)等等。

    MS和全部地场景服务器都保持链接,这样它就成为各个场景服务器间的枢纽,当须要一些跨场景服务器的操做或者须要访问别的场景服务器数据的时候,指令都先发给MS,而后MS根据须要再转发给相应地场景服务器或者直接发给相应的用户,并进行后续地协调工做。

    好比:在场景服务器1上的用户A但愿向游戏中的用户B发出一条添加好友的请求,则场景服务器1向MS发送添加好友指令并附带了用户B的名字,MS查找发现有B这样的用户,则直接把指令发给CLS,而后由CLS转发给B用户;若是没有发现B用户则直接通知A未发现B。

    又好比:在场景服务器1上的用户A点中了传送点,将要传到场景X,场景服务器1发现X场景并不在本身的管辖范围内,因而发送转移指令给MS,MS查找发现场景X在场景服务器2上,因而先发送用户A的离开指令给场景服务器1,让用户退回到MS上,而后再发送用户A的进入指令给场景服务器2,并说明用户将要进入的场景为X,这样一次跨服务器的场景转移就完成了。[1]

 

2.2.2 场景服务器(Zone Server,ZS)

    关于场景服务器的做用,刀剑Online的主程是这么解释的:

    场景服务器(如下简称ZS)就是具体负责游戏场景的服务器。玩家选择人物开始游戏以后就进入了这种服务器(即开始游戏以后CLS把全部玩家的操做指令都转给ZS)。

    玩家的各类操做的逻辑都是由ZS完成的,同时,ZS也要负责各个场景以及场景中的NPC和场景中各个物品的逻辑运行。

    每款游戏的真正游戏性的核心就是这些ZS。它的具体细节我就不过多的讲述了,各个游戏的具体内容应该都不相同。不过有几个原则是共同的:

    1、是要高效。若是ZS对游戏逻辑的处理效率低,会直接影响玩家同时在线的数量,并致使游戏中的玩家感受很“卡”,这是除了网络延时以外第二个会形成游戏 “卡”的地方。提升效率的方法除了对代码进行优化外,就是要使用高效的脚本系统,直接把脚本转化为程序代码编译到程序中去也不失为一个办法。

    2、是要有灾难恢复机制,就是当ZS发生非法操做时(只要不停电)可以恢复出非法操做时各个用户的数据。这个在游戏运营初期服务器尚不稳定的时候很是重要。虽然咱们也能够经过加快用户存盘间隔的方式(好比把每10分钟存一次盘改成每1分钟存一次盘),可是这会成倍加剧数据库负担,同时也不能避免因为用户恰好在存盘间隔的时候得到了重要的金钱或道具而致使丢失的状况。刀剑采起的方法是在申请用户关键数据对象的时候经过包装的函数从共享内存中申请,这样即使ZS非法操做了,共享内存并不会消失,在从新启动的时候就能够从共享内存中恢复出程序非法时的用户数据。固然恢复的时候也须要对用户数据进行一些校验,以避免把已经被破坏的数据存入数据库。[1]

 

 

2.3 设计思想分析

    游戏逻辑服务器主要负责汇总全部在线的client发来的各类操做、状态等数据包,通过一系列的处理后有选择的广播给须要的client,从而给全部在线的玩家呈现一个“统一”的世界。

    优秀的逻辑服务器架构须要优秀的设计思想,而优秀的设计思想又源于对游戏虚拟世界的适度抽象。抽象能够看做一个工程问题,同时也能够看做一个哲学问题,这正是游戏开发的魅力所在。本系列blog将围绕这种抽象一步步展开,结合不一样的项目来诠释网络游戏服务器端的设计思想(但愿能作到....)。

    站在服务器端的角度对游戏虚拟世界进行抽象,首先要弄清楚构造虚拟世界须要些什么?让咱们来想象一下吧(如下内容参照了《盗梦空间》-“Inception”),先来看一段视频:

 

    Inception是我很是喜欢的影片,第一次看到这一段的时候,就感受很是像游戏设计。今天能把它写下来也算没浪费几十块的电影票钱。

    影片中饰演the architect(造梦师)的艾伦·佩姬(女),正在接受莱昂纳多(饰演the extractor-盗梦者)的训练。莱昂纳多说道:“Remember you are the dreamer you built this world。”,“I'm the subject my mind populates it”。值得关注的两个名词:world,subject。这就是咱们要讨论的主题。那么构造网络游戏的虚拟世界须要些什么呢?其实莱昂纳多已经替我回答了这个问题:“We create and perceive out world simultaneously. You create the world of the dream, We bring the subject into that dream, and they fill it with their subconscious.”。world就至关于游戏里的场景,而subject就是一个个在线的玩家(player)。

    游戏世界(world,这里的world泛指游戏世界及地图,见2.3.2)及游戏对象(object,包括player)是构造网络游戏服务器端时,须要关注的两个重点。如何处理好world和object的关系和地位直接影响到服务器端的构架。

 

2.3.1 游戏对象(object)

    先来看游戏里通常会有那些object,以Mangos为例:

class_diagram

图2 mangos游戏对象的class diagram[2]

     对于上面的类层次结构图这里不作展开(留到本系列后面的文章中展开),这里引用mangos的游戏对象是想给读者一个直观的印象,游戏中的object在服务器端是个什么摸样。类的层次是对游戏世界中的对象抽象后获得的结果,抽象须要“适度”:若是类的层次过深,维护起来困难,并且后期每每会致使基类过于臃肿、不堪重负;若是类的层次过浅,一些object的共性不能体现,不少代码会重复出如今各个子类里,复用性差。

 

2.3.2 游戏世界(world)

    游戏世界在本文是泛指游戏对象所在的场景,以及附加在场景之上的地图管理和对象管理,这里统称游戏世界管理。

    如何进行游戏世界的管理是一个复杂的工程问题,网络游戏经常会把整个游戏世界分为若干张地图,每张地图又会分为若干个区域进行管理。这若干张地图能无缝的过渡就称为无缝大地图模式(相似WOW),若是像刀剑Online那样只能经过传送服务在两张不一样地图之间穿梭的,称为有缝地图。而相对来讲服务器端会比客户端简单一些,例:若是在客户端看到的场景是下面这个样子:

yy4

服务器端根据划分区域的不一样可能看到的地图是这个样子:

yy5

    这里服务器端用到了tile-based的方式来管理地图,这种tile的方式在2d游戏中十分的常见(打格子),而不少3d网游的服务器端为了减小运算量也采用这种tile模式划分区域进行管理。若是对地图管理有兴趣请阅读云风的blog《用四叉树管理散布在平面上的对象》、《碰撞检测》,本文先不作详细的展开。

 

 

2.3.3 游戏对象和游戏世界的关系

    如何处理游戏对象和游戏世界的关系和地位,是影响服务器端架构的最直接因素。为了体会到这点,下面将以刀剑Online为例,分析玩家对象(player,游戏对象中最为重要的部分)和游戏世界的关系对整个服务器构架设计的影响。

 

1. 玩家对象player的构建

    刀剑Online把游戏逻辑服务器分为总控服务器(Master Server,MS)和场景服务器(Zone Server,ZS,本文提到的游戏世界多指ZS),那么在client成功登录server后,服务器端应该在哪构建player对象呢?在MS上?仍是在ZS上?这是个工程问题,同时也是个哲学问题.......

  1. 若是把player(以及其余的游戏对象)放在Master Server上:也就是说全部登录的玩家的数据都会在MS的内存中有一份映像,MS将保存着player最新的数据。这么作的好处是player的数据统一,从而使同服玩家的一些交互变得十分方便,好比同服玩家组队、交友等不须要知道玩家在那个ZS里。切换场景服务器时也不须要把player数据从一个ZS拷贝到另外一个ZS,只须要把player身上记的ZS信息修改一下;player在MS上构建的缺点也是显而易见的,全部中心节点式的系统都会遇到单点不可靠的问题,player的信息都保存在MS上,假若有1w人在线,内存占用就是一个很大的问题(毕竟单个进程的内存使用仍是有限制的,实际开发中曾经遇到占用十几G内存的进程......至关可怕),而若是一个player借住外挂发送一些非法消息给MS,错误处理不当时颇有可能会core进程,这样一来整个服务器的玩家都会掉线。还有一些和地图相关的逻辑会很难操做,好比player与场景中怪物进行PK时,因为player的数据保存在MS,所以ZS须要作一个Attach操做告知MS某个player和某个monster进行战斗,MS进行完伤害计算后发AttachResult给ZS,最后由ZS再广播给相关的客户端,如图:image如图中标注的(1)~(4)的步骤,展现了这个过程,编程操做起来仍是挺麻烦的。
  2. 若是把player放在Zone Server上:这是一种比较常见的方式,player和其余游戏对象在场景服务器中构造,隶属于Zone Server。由ZS来掌管player从编程角度讲好处多多:能够直接得到player的信息,对于场景内交互十分便利,场景内的操做不须要多余的数据传输。而在须要跨场景操做时只需用MS中转一下便可;可是这种方式缺点一样是很致命的,player在Zone Server上构造就失去数据放在中心节点的简洁,player的信息经常须要在ZS、MS和DB上进行同步,这种“三体同步”的痛苦只有作过的人才能体会。好比:几个独立的服务器之上须要增长一个跨服服务器,这几个普通服务器的玩家能够进入这个跨服服务器进行pk。那么就须要将player先从普通服务器ZS退到MS,由普通服务器的MS把player的数据发给跨服MS,最后再由跨服MS发送到跨服的ZS。若是还涉及DB则会更加的复杂。ZS、MS、DB相互独立,若是没有强制规定,这种同步每每是没有方向的:player的最新的数据通常在ZS上,可是有些交互不能在ZS上完成,那么能够有两种选择:(1)ZS把player数据发给MS,由MS进行操做,操做过程当中ZS不能修改player的相关数据。(2)把player的最新数据存盘,而后由MS读盘取player的数据而后进行操做,操做过程当中ZS还不能改变player的相关数据。无论哪一种方法开发的逻辑多了之后都很差维护,今后维护人员都变成怨妇.......

    刀剑Online应该采用的是第2中方式,把player放在Zone Server上。

 

2. 玩家对象player和world的关系

    如何处理玩家对象player和world的关系集中体现了服务器端的哲学,值得细细品味。处理二者的关系在本人看来有两种主要的模式:一种是以player的中心的“自我”模式;另外一种是以游戏世界为中心的“上帝之手”模式。这两种模式的哲学主要体如今加锁模式上,接下来进行详细介绍:

(一)player的“自我”模式

    “自我”模式顾名思义,就是player以自我为中心,什么事都要亲历亲为。以player使用物品为例:

image

    如上图是一个完整的player使用物品的流程。“自我”模式体如今上图的(1)位置,在(1)中首先取到服务器中对应的player,加锁后用player调用本身的处理函数进行处理。因为(1)至关于处理客户端发来的请求的入口处,所以在这里进行加锁和解锁操做是十分合适的,此后的(2)~(5)player在调用本身的处理函数时,编程人员彻底不须要考虑加锁的问题。

    所谓的“自我”模式,其实就是指在服务器端对player的操做其实都是player本身去完成的,“我”本身去把事情作完。处理逻辑时“我”(本身的player实例)不会主动去锁住对方的player实例,所以一个player不能修改另外一个player的数据,world也不能修改player的数据。world和player的关系是“独立”的,player身上记着world的信息(至关于“我属于哪一个世界”),world里存放着player的实例,可是它们直接不能直接修改对方的数据。这么作是为了不死锁,使得加锁解锁变得简单而统一(如上图)。

    “自我”模式有加锁解锁的便利,可是一个网络游戏怎么可能player和player、player和world直接没有交互呢?交互就须要得到或修改对方的数据,遇到这样的问题“自我”模式怎么处理呢?请看下图:

image

    如上图:client A向服务器发送“向玩家B发起攻击”的消息,服务器端client A对应的实例Player A收到消息后,发现须要与player B进行交互,根据“自我”模型的限定,player A不能直接修改player B的血量等信息,这时player A须要作的是将本身的信息打包成一个msg结构而后push_back到消息队列message queue并指定player B做为接收者。在message queue上将这个msg转发给player B前,会先调用lock(playerB)将B锁住,接着把msg传给player B进行处理,在处理完毕后再调用unlock(playerB)。lock和unlock都在统一的地方调用,加锁解锁十分简洁,playerB处理msg消息时不用关心任何加锁问题。在上图的(2)中,player B进行伤害计算后将伤害消息广播给周围的玩家。

    注意:“自我”模式下,伤害计算是在受攻击方进行的。player之间、player和world之间都是经过消息传递进行交互的。

总结:

优势:“自我”模式的优势集中体如今加锁上,编程人员在编写具体逻辑时不用担忧加锁问题,也不用费尽心思来避免死锁,由于加锁都在消息入口处统一作了,同时不会主动去对别的player实例进行加锁操做。使用这种模式时,最好把移动的相关逻辑独立出来使用不一样的锁,以避免照成过多的加锁冲突和等待。

缺点:“自我”模式的缺点也是十分明显的,每一个player实例做为不一样的“我”独立存在,须要交互时只能经过消息队列来传递信息,极大的增长了交流的成本(内存、CPU占用率都会增长)。一个player每每不能即时的取到别的player的数据,这样一来不少的计算都只能作延后处理,好比伤害计算就只能放在被攻击者的player上,使得不少须要作先验判断的技能实现起来变得复杂,这类技能只能靠释放技能的player,发送请求消息给其余player,而后再由其余player把本身的信息经过msg queue发给释放技能的player。异步处理方式每每会但来更多的烦恼——须要增长不少错误判断、错误处理以及超时处理等等。在线人数高的时候,message queue的容量以及所占用的内存也是须要考虑的问题。

    引用狄更斯的:“It was the best of times, it was the worst of times”。本人依葫芦画瓢:“自我”模式是一种nb的设计,也是一种sb的设计.......

 

(二)world的“上帝之手”模式

    “上帝之手”模式是以world为中心(相似war3),以地图为单位进行划分,player、NPC、monster等游戏对象都隶属于world。在world的掌控之下,就像有一只上帝之手在拨弄着这些小玩意。以client A向服务器发送“向玩家B发起攻击”消息为例,服务器端的处理流程以下:

image

    从上图中能够看出“上帝之手”模式的核心是world去完成这项任务:找到playerA和B,把他们都锁住,而后交给技能模块来进行伤害计算,最后把结果广播出去。整个过程就像在玩war3这样的RTS游戏,服务器就像一个神,以斜45度的上帝视角来观察全部的玩家。

    “上帝之手”模式的设计难点在于加锁策略,由于须要对多个对象进行加锁,加解锁的顺序不当容易产生死锁。加锁策略有不少种,这里不作具体的展开。只介绍一种最经常使用的方法,即对加锁对象进行排序。游戏对象在产生时都会有一个惟一的id作标识,当lock ()函数能按照一种稳定的算法对这些id进行排序时,就能够避免死锁。好比对游戏对象进行分类:player、npc、monster等,先对分类进行排序,再对对象的id进行排序。须要注意的是MMORPG游戏常常须要进行合服操做,为了保证合服后player的id不会重复,须要对player_id进行一些规划。

总结:

优势:服务器程序扮演上帝的角色,能够得到几乎全部的信息,这对逻辑编写带来极大的便利。和“自我”模式同样,最好把移动的相关逻辑独立出来使用不一样的锁,以避免照成过多的加锁冲突和等待。“上帝之手”模式更容易进行总体规划,更容易实现模块化设计,下降程序的耦合度。

缺点:上帝也不是这么好演的,“上帝之手”模式对总体框架、接口设计、加锁策略等有更高的要求,同时对编程人员的要求也会更高。

 

 

网络游戏服务器构架设计(四):云风的轨迹

    最近闲着没事把云风的《开发笔记》看了个遍,但愿能从大牛的开发轨迹中获得一些启发。但多是由于本人level过低,一遍看下来仍是云里雾里,不甚明白。没办法只好再看一遍,但愿能对他们的服务器端架构有个简单的认识,这里同时作些笔记。

PS:本文是我我的对云风的开发笔记的读后感,可能会有不少错误,慎入!

 

----------------------------------------华丽丽的分割线-------------------------------

 

1、服务器划分原则  

    在现有的网络游戏服务器端架构中,可能是以功能和场景来划分服务器结构的。负载均衡和集群暂且不在本文中讨论(bigworld、atlas)。服务器划分能够基于如下原则:

  1. 分离游戏中占用系统资源(cpu,内存,IO等)较多的功能,独立成服务器。
  2. 以多线程或多进程的编程方式适应多核处理器。
  3. 在同一个服务器架构下,应尽量的复用某些服务器(进程级别的复用,好比场景服务器)。
  4. 运行时玩家数据的保存、修改及数据流向应该是设计的焦点,它同时也决定了服务器应该如何划分。
  5. 服务器的划分应该适度,在保证清晰的数据流向的前提下,根据游戏的类型和规模尽可能减小服务器或服务器进程的个数,以减小服务器之间过多的复制数据、锁冲突(使用共享内存进行通信时)。
  6. 主要按照场景划分进程,若需按功能划分,必须保持整个逻辑足够简单,并知足以上一、3两点[1]。

 

    接下来咱们来看看云风的服务器架构是如何处理好以上几点的。

image

图1 服务器架构(此图为本人猜想,可能有误)

 

2、运行时的玩家数据

    网络游戏服务器程序一项重要的工做就是根据client发过来的数据包,在服务器端模拟玩家的行为操做并把这些行为广播出去。那么服务器程序在运行时就须要一些实体来保存玩家的数据,这些实体能够是一个类,也能够是一个线程,设计思想不一样采用的实体差异也会很大。这里涉及服务器端设计的一个核心问题:运行时玩家数据的保存、修改及数据流向。

 

agent

    云风经过抽象实体agent来处理单个client的服务请求,agent和client是1:1的关系(见图1)。agent是在gate程序后端,负责翻译、转发以及回应客户端发过来的请求。agent的主要工做内容见云风的《开发笔记 (1)》。值得补充的是设计agent的主要优点是:

把对单个 client 服务的代码集中写在 agent 服务中。由 agent 再和内部其它服务沟通。数据加载使用共享内存的方案,由 agent 向持久化模块发出信号,作加载或纯盘处理,经过共享内存获得结构化数据块。[2]

    agent至关于client在服务器上对应的实体,玩家的属性和数据只能由agent来修改,别的服务只有读权限。经过attach操做得到数据(attach多是经过服务器通信框架skynet,也有可能直接mmap到共享内存sharedb上以得到数据)。

    agent的设计使得整个系统对玩家数据的修改只有一个输入点,数据流十分的明确,易于维护。虽然这种设计可能会照成数据的屡次复制,可是带来的代码维护和查错上的便利是十分可观的。

    若是把全部的agent放在同一个进程里,在编程该程序时还应该考虑到容错问题,好比说(1)使用C++编写这个程序,agent以类的形式存在,使用thread pool来处理收到的数据包,实际操做时thread的数量是会远远小于agent的数量的,数据包到达后会在队列里等待thread调用agent的逻辑来处理。这是一种比较常见的设计方法,但要注意的是因为agent都放在一个进程里,程序的健壮性要求很高,一个进程core则会致使全服玩家掉线。而使用C++编写也增长了宕进程的可能性……..你懂的。(2)使用Java编写,对于这种“中心节点”式架构来讲多是更好的选择,起码不是由于一个玩家的误操做(可能使用外挂)致使全服玩家掉线。(3)云风彷佛是使用lua coroutine来实现agent的相互隔离和协同工做的,这样能够减小单一agent失败对其余agent的影响(动态语言的好处)。

 

sharedb

    sharedb在系统中的地位看上去像是database前端的cache,但就本人的理解sharedb的做用远不止是一个数据缓存。

    和天龙八部的ShareMemory相似,sharedb也采用了定长的结构化数据(见《开发笔记 (6)》),经过共享内存来实现进程间的数据共享。sharedb的存在使得游戏逻辑处理和数据保存逻辑获得很好的隔离,游戏逻辑不用关心后端的数据是如何保存的,只要sharedb挂上按期存盘的服务,在接口定义明确的状况下,后端到底采用什么样的数据库变得不是那么重要,从而下降了系统的耦合度。

 

3、服务器底层框架skynet

    skynet的设计思想见《Skynet 设计综述》:

    我但愿咱们的游戏服务器(但 skynet 不只限于用于游戏服务器)可以充分利用多核优点,将不一样的业务放在独立的执行环境中处理,协同工做。这个执行环境,最先的时候,我指望是利用 OS 的进程,后来发现,若是咱们一定采用嵌入式语言,好比 Lua 的话,独立 OS 进程的意义不太大。Lua State 已经提供了良好的沙盒,隔离不一样执行环境。而多线程模式,可使得状态共享、数据交换更加高效。而多线程模型的诸多弊端,好比复杂的线程锁、线程调度问题等,均可以经过减少底层的规模,精简设计,最终把危害限制在很小的范围内。这一点,Skynet 最终花了不到 3000 行 C 代码来实现核心层的代码,一个稍有经验的 C 程序员,均可以在短期理解,作维护工做。 
    作为核心功能,Skynet 仅解决一个问题: 
    把一个符合规范的 C 模块,从动态库(so 文件)中启动起来,绑定一个永不重复(即便模块退出)的数字 id 作为其 handle 。模块被称为服务(Service),服务间能够自由发送消息。每一个模块能够向 Skynet 框架注册一个 callback 函数,用来接收发给它的消息。每一个服务都是被一个个消息包驱动,当没有包到来的时候,它们就会处于挂起状态,对 CPU 资源零消耗。若是须要自主逻辑,则能够利用 Skynet 系统提供的 timeout 消息,按期触发。 
    Skynet 提供了名字服务,还能够给特定的服务起一个易读的名字,而不是用 id 来指代它。id 和运行时态相关,没法保证每次启动服务,都有一致的 id ,但名字能够。

    本人感受skynet像一个发布订阅的消息中间件(还没看源码,可能有误),这种基于服务的即插即用式的框架给服务器端带来很大的可扩展性,同时也使得各模块之间独立清晰,具备良好的可维护性。可是这里有个疑问,服务都以so的形式挂在skynet上,那么这些服务从哪里获取玩家、怪物、NPC等object的数据?是从skynet中得到仍是直接从sharedb中得到,出于性能的考虑是否是要把skynet和sharedb部署在同一台物理主机上?这样一来就会增长设计和具体逻辑的耦合度。看了《Skynet 集群及 RPC》,感受skynet上的服务是要经过skynet来得到玩家的数据,这样操做会不会致使数据被复制不少次,不知道最终的效率是否受到影响?

 

4、gate

    知足服务器划分原则里的第一点:分离游戏中占用系统资源(cpu,内存,IO等)较多的功能,独立成服务器。

    gate的主要工做能够参见本系列blog的第二篇《网络游戏服务器构架设计(二):刀剑Online - 链接负载服务器CLS

 

5、场景管理器

    主要用于管理静态场景和动态副本,好比agent登陆时查询本身所在场景对应的服务器地址。

 

6、场景服务器

    场景服务器的内容我没有从《开发笔记》中获得太多的信息(可能level过低),更多的是以功能模块的形式写,好比AOI。不过其中有一点比较新颖的是云风认为player的位置、动做状态,战斗数值状态等都是场景的一部份,应该保存在场景中而不是agent中。本节有所更正见(八)补充。

    据个人猜想,场景服务器应该会负责:

  1. 怪物行走控制,player移动更新及位置同步
  2. 怪物AI策略
  3. 区域性广播,场景广播
  4. 战斗逻辑
  5. AOI服务(Area Of Interest )
  6. 碰撞检测
  7. 自动寻径

    须要注意的是场景服务器修改的一些数据应该以什么样的频率通知agent呢?好比player的位置信息,该信息是彻底保存在场景数据里仍是说agent里也有一份?

 

7、总结

    本文是一篇云风《开发笔记》系列blog的读后感,所述内容均是本人的猜想,虽恐贻笑大方,但也但愿能抛砖引玉。收笔忐忑ing!

 

8、补充:

  (1) 云风在微薄上的回复是:“咱们最终采用的是单进程多线程, 每线程上一个 lua state 的结构. sharedb 是用来线程间数据交换的. gate 和 sharedb 以及 loader 和 agent map 同样, 都是 skynet 下的独立服务, 以 so 挂接进去的. 后来的商品交易, 掉落品分配也是 skynet 下的服务. ”

  (2) 关于场景服务器,云风已经给出完整的说明,见《开发笔记(14)

    场景服务分红两个部分,一是副本管理器,二是地图服务。在角色数据上,记录有角色应该属于的地图。agent 向地图所属的副本管理器查询,获得他所属的地图服务地址。即可以把本身注册到具体地图上。 
    地图服务管理了全部其中的角色 id ,以及若干 npc 。他的义务在于把让这些 id 对应的 agent 相互了解。但具体逻辑则放在每一个 agent 服务上。每一个 agent 本身所属进程 attach 其它 id ,能够获取其余对象的状态。

  .........

  (3) 风哥在《开发笔记(25)》中已经提到最终使用单进程多线程的模式。看来简单设计是有共识的:-)

相关文章
相关标签/搜索