再谈游戏服务器架构

一、服务器划分原则

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

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

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

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

二、运行时的玩家数据

网络游戏服务器程序一项重要的工作就是根据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挂上定期存盘的服务,在接口定义明确的情况下,后端到底采用什么样的数据库变得不是那么重要,从而降低了系统的耦合度。

三、服务器底层框架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来获得玩家的数据,这样操作会不会导致数据被复制很多次,不知道最终的效率是否受到影响?

四、gate

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

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

五、场景管理器

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

六、场景服务器

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

据我的猜测,场景服务器应该会负责:

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

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

七、总结

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

八、补充:

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

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

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

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2016-06-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏SDNLAB

ODL Lithium SR2版本Entity Ownership Service分析及OFplugin规模部署可用预测

家好,我是盛科网络负责sdn研发的张东亚,作为sdn设备的提供商,业余非常关注sdn生态圈的发展,最近抽时间研究了li版本of plugin的代码,记录了一些心...

3175
来自专栏Golang语言社区

前后端分离开发模式下后端质量的保证 —— 单元测试

概述   在今天, 前后端分离已经是首选的一个开发模式。这对于后端团队来说其实是一个好消息,减轻任务并且更专注。在测试方面,就更加依赖于单元测试对于API以及后...

4579
来自专栏Java架构

高并发风控技术解密(下)

1874
来自专栏鸿的学习笔记

聊聊分布式系统的时钟问题

诸如此类的问题,还能提出很多,因此需要一个靠谱的时钟来保证分布式系统里事件的处理不会出错。

1371
来自专栏CSDN技术头条

高性能智能日志实践

本文作者是 Archanaa Panda ,从 2000 以来一直在软件开发(构架、设计和编程)团队担任 Java / JavaEE 构架师,目前立志于做一个与...

25210
来自专栏VMCloud

【解析向】腾讯云的Windows Server日志配置收集工具是个什么鬼?(1)

楼主在使用腾讯云IaaS时,经常遇到一些疑似平台问题的Windows疑难杂症,通常会向腾讯云工单提交OS工单,让其专业工程师来排查,毕竟我买IaaS的CVM要来...

61216
来自专栏用户2442861的专栏

分布式统一框架的设计与实现(数据库)

我们设计并开发了内容中心统一的分布式开发框架。我们把它取名为albian, albian是基于java的(故以下简称albianj)。他主要是面向海量数据处理...

3021
来自专栏华章科技

从 Python 转到 Go 语言的五大理由

“ Python 是非常强大的,特别是 Python3 有了异步功能,但是 GO 将完全取代它在大企业中的存在…”如果你真正理解了引号中的话,你可能会去尝试 G...

1573
来自专栏Brian

Effective Debugging-高效调试

概述 最近在看《Effective Debugging》,作者(Diomidis Spinellis)将30多年的系统开发和调试的经验融入到书中,从策略、方法以...

3618
来自专栏Java架构

高并发风控技术解密(下)

  •从业务中抽象及通用——如果一种业务有可能在今后重复出现,那就将其模块化,系统化(如批处理系统),发展成为平台能力

1695

扫码关注云+社区

领取腾讯云代金券