从后台开发的角度对游戏开发过程、后台技术架构两方面的要点

从后台开发的角度对游戏开发过程、后台技术架构两方面的要点

之前一直从事企业软件开发,按照传统软件工程方法参与了大量软件系统的建设。 加入IEG这几年,我有幸与很多优秀的游戏团队合作,通过产品比较总结了相关的差异。 这是从后端开发的角度,对游戏开发流程和后端技术架构的要点进行的简单总结和回顾。 希望能够启发新的思路,也能让游戏行业的新手快速全面的了解游戏后端的开发方法,因为写得比较粗糙。 ,请大家多多批评和指导。

1. 开发方法简述

首先需要提到的是产品开发模式(当然开发方式不限于后端)。 过去,传统行业的项目或产品都是通过需求分析、业务建模来跑; 概要设计、详细设计、编码测试、集成测试、上线等过程中,系统分析和设计时间占整个项目周期的60%以上。 基本上详细设计几乎都是伪代码,正式上线后功能会保持稳定,后续版本变更的周期也比较长。 。 这种方法就是传统的瀑布模型。 基本流程可以简化如下:

http://km.oa.com/files/post_photo/929/224929/330d9d3dc2664260cdceae11010234881418262511.png

传统的瀑布模型实际上存在几个问题:

1、需要准确理解业务需求,导致分析周期过长;

2. 由于依赖关系,难以准确评估开发时间;

3、整体进度难以控制,容易造成延误;

我们熟悉的游戏开发一般采用迭代模型,因为不先玩游戏就无法评估产品。 迭代的优点是显而易见的:它可以立即从结果中反思设计。

如下所示:

http://km.oa.com/files/photos/captures/201412/1418261778_53.jpg

在迭代开发的过程中,你需要花费大量的时间去玩游戏,所以你需要对游戏进行原型设计。 事实上,这个原型也可以是非数字化的。 例如,某些类型的游戏可以使用纸模型或卡片来恢复。

迭代开发模式也称为“敏捷开发”,是游戏行业标准的开发模式。 一时间,最流行的敏捷开发方法是SCRUM。 SCRUM方法中最重要的是Sprint的概念。 简单来说,就是指定一个短期的时间(比如我们每周一次)来交付一个功能可测试的版本。 该版本中需要实现的功能特性称为Sprint Backlog(最终发布功能的子集)。 每次冲刺后都需要重新评估功能需求。 本文不深入讨论具体的 SCRUM 方法。 如果您有兴趣,请google一下。

http://avocado.oa.com/fconv/files/201412/4f9adb82fa6b2be0563270b82a27e008.files/image003.png

敏捷方法

可见,对比几种开发模式,可以看出游戏开发的需求变化和版本发布周期一定更加剧烈。 产品体验如果需要积累后端经验,就需要不断思考、重构和提炼。 不仅要提升技术抽象水平,还需要从业务层面抽象来积累和强化自己的游戏开发思维。

基于此,我们从游戏产品的角度对传统企业软件进行了比较,并从技术和业务两个方面进行了以下回顾和总结。

2. 游戏背景架构

游戏后端与其他产品相比有什么特别之处? 在做游戏之前,我也很好奇。 回顾过去,传统软件已经形成了很多架构应用模型,如Client/Server、Browser/Server、3Tier、MVC模型等,在演化过程中,传统软件架构从早期的两层架构演变而来改为三层架构,如下图:

http://avocado.oa.com/fconv/files/201412/4f9adb82fa6b2be0563270b82a27e008.files/image004.jpg

http://avocado.oa.com/fconv/files/201412/4f9adb82fa6b2be0563270b82a27e008.files/image005.jpg

由于使用的是关系数据库,因此上述架构在事务性交易系统中最为常见。 这也是很多早期互联网应用的架构原型,比如Client层改为Browser,业务逻辑层改为WebServer+ApplicationServer,最后是DatabaseTier。 例如LAMP技术中的ApacheWebServer/Mysql/PHP等。

随着互联网的发展,技术体系的架构开始不断发展和创新。 与传统应用场景相比,具有明显的特点。 由于应用场景的复杂性,需要不断探索各个层面的技术深度。 比如后端需要面对以下两个核心问题:

l 网络环境:与传统的企业应用相比,客户端不再集中在局域网环境中,而是分布在互联网的各个角落。 有的通过ADSL接入,有的通过Cable接入,有的通过无线接入; 他们的地理位置也相差很大,对应的运营商也千奇百怪。 这个时候我们面临的就是访问问题。

l 用户数量:由于用户群体和使用方式的限制,传统企业应用只会有一定的访问次数,例如银行ATM。 受网点数量限制,用户需排队办理。 互联网应用已经完全摆脱了这个限制,游戏也面临着大量的问题。

网络游戏相对常见的互联网应用有以下区别:

l 更强的实时应用交互性:网络游戏具有丰富的表现力和堪比现实的虚拟场景游戏后端开发,尤其是主机游戏和大型客户端游戏。 玩家成为游戏中虚拟世界的领导者,需要与游戏环境实时交互并接收反馈。 因此,如何设计游戏的逻辑层次就变得非常重要。 哪些游戏逻辑应该放在客户端,哪些应该放在服务器端,需要仔细考虑。 这也成为决定游戏架构的核心问题之一。

l 更强的多人互动体验:不可否认,网络游戏如此吸引人的原因之一就是虚拟世界中人与人之间的互动所产生的情感联系。 当你身处游戏世界时,与你互动的头像背后是分布在世界各地的不同真实面孔。 如何让你的游戏高效处理多人状态同步,准确判断多人交互事件的结果,也是游戏开发所追求的独特技术能力。

那么,总结以上游戏产品的技术需求,从后端的技术层面来看,常见的架构可以抽象为以下几个简化的层面:

http://km.oa.com/files/photos/captures/201412/1418300752_10.jpg

上图中,整个游戏服务器由五个抽象层组成。 这些功能抽象层可以共存于单台机器上,也可以分布式; 根据实际应用场景,将一些层合并为一个进程。 因此,可以根据应用场景灵活组织实施。

需要注意的是,各个抽象层的数据交互通常是使用异步处理完成的。

我们来看看每个抽象级别需要面对的问题和常见的解决方案。

1、接入层

分离接入层的主要目的是分别关注以下内容:

网络问题针对性解决方案:

例如,在上述复杂的网络环境中,接入层需要处理以下问题:

使用什么通信方式与客户端通信,TCP还是UDP,长连接还是短连接?

面对大量用户时如何接收更多的客户端连接?

如何更高效地处理客户端请求的分发?

如何高效定位网络层异常问题?

如何处理跨运营商访问问题?

它是如何部署来处理请求的负载平衡的?

您可以针对上述任何问题撰写深入的技术分析。 在此,作为一篇总体总结和回顾性的文章,我只提一下原则内容。 正因为上述问题纯属技术细节,与业务层无关,所以接入层通常被设计为游戏后端的独立网络服务器。

接入层通常是Socket Server守护进程,支持多种连接方式,通过配置支持是通过长连接还是短连接提供服务。 它还支持通过配置打开TCP或UDP协议的服务。

从性能上来说,基于Linux的接入层服务器通常采用epoll+多进程服务的形式。 有些是通过重新发明轮子或者重用开源组件Libevent来实现的。 作为Linux后台开发者,还必须知道如何调整操作系统参数,设置网络层相关配置,比如最大连接限制,或者内核中net.ipv4.tcp的相关参数。

关于跨运营商访问的问题,早期采用的是支持多运营商线路的机房,也有在多个运营商机房部署业务的方法。 另外还需要DNS解析支持。 例如,在连接联通的客户端上游戏图片素材,DNS会将其连接到联通线路的服务器等。现在通常使用TGW公司来解决这个问题。 更方便地处理多路接入和IP融合。 具体可以通过KM了解TGW的功能和特点。

更灵活的访问控制:

在设计游戏后端时,首先要考虑的是玩家账户数据的管理。 拥有帐户后,您将面临登录身份验证过程。 在腾讯,大量的用户自然形成了完整的用户账号体系,外部联合运营平台也对用户账号体系负有全部责任。 因此,游戏产品通常会接入游戏发行平台的账户系统。

我们把接入层分离成一个独立的服务进程后,留给它处理第二个任务,就是和分发平台的账户系统集成,比如接入公司的MSDK进行认证处理。 将身份验证交给接入层有几个优点:

首先,接入层控制访问权限,未经身份验证的客户端请求将被第一步拒绝。

其次,接入层作为认证服务的客户端,单独处理认证平台的权限控制。 例如,仅接入层机器需要向第三方认证平台机器开放网络访问权限。

第三,接入层可以将认证处理封装到可配置或动态可加载的模块中。 这样,访问一个平台只需要轻松加载一个认证模块,不会对其他系统产生任何影响。

第四,接入层和客户端定义通信协议。 探测包、异常格式包、非法数据包将无法到达核心业务逻辑层; 协议的抗重放处理可以在接入层进行处理。

与逻辑层分离,减少耦合:

接入层独立后,与客户端的网络层处理与逻辑层无关。 接入层负责与客户端进行通信。 通信协议、通信方式、通信数据的字节顺序和格式均由接入层控制。 全面负责。 接入层只需将接收到的合法数据传递给通信层,逻辑层职责单一。 只需要从通信层获取请求,并根据命令字处理业务逻辑。 由于减少了耦合,出现了明显的优势:

首先,逻辑层和接入层的维护互不影响。 例如,更新逻辑层代码、重启逻辑层都不会影响接入层的客户端连接状态,也不会因为进程重启而强制断开客户端连接。 打开。

其次,可以最小化接入层的变化对业务逻辑的影响。 例如,可以单独优化服务器和客户端之间的通信。 只需要在接入层增加压缩处理并打开压缩开关即可增加通信层的压缩处理。 达到减少带宽的目的。

简化的接入层业务流程结构简化如下:

http://km.oa.com/files/photos/captures/201412/1418261721_19.jpg

针对负载问题,接入层通常提供统一的接入分配处理。 例如,游戏分区后,接入层还需要根据游戏的开服方式提供选服服务。 这就是目录服务器(我们喜欢称之为DirServer)。 事实上,目录服务器也属于接入层的服务器模块。

相信不用说大家都知道,在互娱研发部提供的公共组件中,TConnd是很多产品应用的接入层组件。

需要注意的是,如果是多客户端实时同步的游戏(比如帧同步模式),可以将接入和逻辑放在一起,更快地响应同步逻辑。

2、逻辑层

企业级应用程序的逻辑层通常部署或运行​​在容器(Container)中,抽象出资源接口(Resource Interface),让逻辑层专注于业务流程实现。 游戏后端的逻辑比较简单且多变,但是游戏客户端的Game Engine和相应的基础设施与这个概念比较相似。 如果从逻辑层面抽象的话,形成平台和脚本接口的概念是一样的。

游戏后台逻辑层的逻辑实现通常可以分为两部分。 首先是根据核心玩法建立的主要游戏逻辑,我们可以称之为核心逻辑。 其次,除了核心玩法之外,通常会围绕游戏的经济提供其他玩法。 系统展开丰富游戏体验,堪称外围逻辑。

逻辑层的技术实现与游戏的核心玩法密切相关。 不同的玩法会导致逻辑层架构的实现方式完全不同。 但可以肯定的是,逻辑层的核心任务是与玩家属性相关的状态控制。

如下图所示,逻辑层通常以服务器守护进程的形式表达,它从通信层获取客户端请求,然后根据命令字处理相应的用户状态处理逻辑。 比如注册是创建用户数据,登录是拉取用户数据并认证,行走是修改用户坐标等。图中红框就是Main Loop。

http://km.oa.com/files/photos/captures/201412/1418300870_6.jpg

从技术角度来看,逻辑层的任务仍然离不开Input/Process/Output的步骤:接收客户端请求,根据请求处理相关业务逻辑,返回处理结果。

但由于游戏服务器需要承载足够多的玩家,并且需要实时同步状态到游戏场景的特点,对请求的响应有强烈的需求,所以逻辑层需要精心设计来实现最好的表现。

通常,逻辑层的服务会按照功能粒度拆分为单功能组件,进行异步交互,以提高效率。 这样做的好处是将串行任务步骤拆分为多个子任务步骤。 核心游戏循环控制任务步骤的顺序,并将子任务步骤分发到相应的各个功能服务组件进行分布式处理,实现共享计算。 量化的效果可以有效提高逻辑层的吞吐量。 单进程串行处理升级为分布式异步处理的基本架构如下:

http://km.oa.com/files/photos/captures/201412/1418300986_75.jpg

在上图右侧的架构中,Logic Server可以看作是Master,而专门处理某些任务的服务器可以看作是Worker。 以某款游戏为例,逻辑层按照业务功能可以分为区域服务器、地图服务器、PvE服务器、PvP服务器、排名服务器等。 ZoneServer区域服务器可以看成是Master服务进程。 用户的请求首先到达这里,然后根据特定的功能将处理分发到相应的专用逻辑服务器组件进行处理。

因此,逻辑服务器可以是单进程的形式,也可以是多进程的形式。 由于线程安全和竞争带来的复杂性,多线程很少被使用; 并且因为拆分任务步骤后也达到了类似的并发效果。 ,所以对多线程的需求并不强烈。

如上所述,逻辑层的结构可以将同步串行分解为异步并行,同时通过分区来解决海量问题和性能问题。

关于玩家数据的状态控制

对于服务器来说,状态判断是指来自同一发起者的两个请求在服务器端是否存在上下文关系。 如果是有状态的,服务器一般需要保存上下文相关的信息,每次请求都可以默认使用之前的状态信息。 但无状态请求是不可能的。 服务器能够处理的逻辑必须全部来自于请求中携带的信息以及服务器本身保存的其他公共信息,并且可以被所有请求使用。

无状态

很多游戏都使用无状态,简单实用。 每笔交易都是无关的。 上下文信息存储在用户数据对象中。 每个请求都会拉取播放器数据,处理后写回逻辑记录状态。 但无国籍也有其固有的缺陷:

1、实时交互:由于不在线维护玩家的状态(即上下文),无法获知用户的在线状态,无法进行实时交互,例如实时聊天;

2、并发修改:并发请求或者多人交互时,可能会出现数据状态读写顺序的问题。 例如,如果两个玩家同时PK第三个玩家,则无法控制哪个玩家先修改第三个玩家。 因此,无状态服务需要提供锁服务器来控制数据的并发写入。

有状态的

也就是说,玩家对象的上下文状态是在服务器上维护的。 客户端和服务器之间的交互允许上下文关联(并且还控制访问顺序)。 服务器逻辑可以轻松主动地触发玩家数据的状态。 但有状态服务器有几个关键问题:

1、状态迁移:由于现网的容灾问题,单个服务组件通常会部署多台机器来提供服务,这就需要对玩家对象的状态迁移进行严格、细致的处理。 例如,一个玩家只能同时存在于一台服务器上。 以上,就是踢人逻辑。 否则会导致状态混乱。

2.状态同步:由于玩家对象的数据状态是在逻辑服务器上维护的,这就产生了状态同步到数据层的问题。 如何保证状态一致性或者实时同步也需要仔细控制,否则用户数据将会回滚。

逻辑层服务通常采用并行扩展的方式来扩容。 如果是有状态服务,由于状态迁移的问题,在扩容时通常需要停止。 无状态服务可以在不停止服务器的情况下进行扩展,因为不存在状态迁移问题。

逻辑层的部署需要考虑各功能模块的硬件资源消耗情况进行统一规划,如吞吐量、负载能力、网络流量、CPU/内存/磁盘消耗等。 通过TPS/内存消耗可以有效设计各功能模块的配比。

另外游戏后端开发,需要提醒我们的是逻辑层公共模块的部署形式。 建议使用统一的无状态公共服务集群来服务所有游戏区域以控制成本,而不是单独提供每个区域。

3、通信层

与企业级应用相比,游戏后端的通信层抽象相对简单。 理想的通信层用于解耦业务组件,只提供简单的API调用,然后让程序员专注于业务逻辑。

通信层可以以通信库、通信服务进程或单独的通信模块组件的形式表达,例如ACE/ZeroMQ、TBus或Router组件。

我认为通信层关心的主要问题有以下几个:

l高性能

为了实现高性能,通信层必须竭尽全力。 首先,游戏后端各组件的运行环境高度一致。 与企业级应用需要考虑Legacy System不同,您只需要专注于挖掘指定环境的细节,即只需要在Linux中同时考虑同机通信和跨机通信环境。 表单的效率确保以最有效的方式处理通信。 比如本地通信可以使用IPC中的共享内存,跨机通信只能使用Socket长连接。 此时,开源的ZeroMQ可以通过代码选择IPC/TCP等形式。 例如,使用IPC通信时,如果发现通信是在本机,由于是跨平台通信层,所以会自动使用Unix Domain Socket方式进行通信; 而 TBus 则无需考虑异构情况,直接使用 Shared Memory 方式。

其次,提高性能就是吞吐量的问题。 通信层需要适当的数据包合并和分包机制,并以最合适的数据包大小和读写频率来控制通信效率。 至此,TBUS应该已经处理好了,但是在没有看到源码的情况下,我不确定它是如何处理的。 另外,很多通信库也考虑零拷贝的问题。 Nanomsg在其功能中使用了RDMA(远程直接内存访问),允许它直接从用户空间发送到网卡; 一些通信库在共享内存中使用相同的块。 内存通信达到零拷贝的效果。

l稳定性

作为通信层,最重要的职责是保证消息的传递。 通信层必须提供冗余机制,以保证消息真正被处理并按顺序处理。 因此,满足这个条件的通信层必须提供消息持久化处理。 通常的实现方法是提供一个持久的消息队列来保存消息数据。 消息必须被成功处理才能从队列中删除。 例如ZeroMQ2.x也将Queue放在内存中,存在崩溃时丢失的问题; 而IBM Websphere MQ提供了多种持久化方法来保证消息的完整性。

在很多消息队列使用的“插入-获取-删除”范式中,在从队列中删除一条消息之前,你的处理过程需要明确表明该消息已经处理完毕,以保证你的数据安全。 保存它直到你用完它。 同时需要确认一条消息只能被处理一次,即只能投递一次。 此时TBus还提供了不删除模式,比如查看队列中的消息而不删除它们。 这种情况下,可以理解为将消息从队列中取出,然后放回去。

此外,通信层组件还需要确保可恢复性。 例如,通信进程被杀死后,服务启动后需要能够从中断点继续运行。

l多功能

一个完整的通信层组件需要支持多种通信方式,例如同步、异步通信; 往上一层,可以通过商业级MQ的功能来比较这些模式的支持情况,比如各个MQ提到的几种模式:

nPoint-Point:点对点,即发送方与接收方之间的通信模式

nRequest-Reply:请求/响应,消息的消费者必须响应生产者的处理结果

nPublish-Subscribe:发布/订阅,消息根据Topic订阅进行消费。

很多只提供P2P模式的消息中间件通常都是简单的FIFO队列。 消息接收方需要使用轮询的方式主动查询获取消息,导致逻辑响应处理延迟。

l易于管理

前面提到,形成通信层组件的主要目的是解耦,让通信双方只关注业务逻辑。 由于通信环境和通信方式要求的差异,通信层组件需要提供易于管理的应用接口,并在运行过程中提供方便的错误检查和监控功能。

从通信组件易于管理和应用的角度出发,业务逻辑层重点关注以下几点:

它是否提供了一个简单的应用程序接口,可以通过编程方式控制通信模式?

是否可以灵活控制通信通道,如动态建立、权限控制等

支持消息的事件通知回调,是否还需要轮询检查

有水位控制吗? 当队列已满时如何有效响应?

是否可以轻松监控消息流量?

游戏后端面临的最常见的管理问题就是扩容和缩容操作。 在这个过程中,需要添加后台服务组件。 这时,如何方便地建立通信层关联就需要通信组件的支持。 沟通渠道是否需要重建? 重建过程是否需要停止等?

4.数据层

游戏后台数据层用于持久化玩家数据。 由于玩家数据之间的弱关联性,与其他互联网应用一样,也逐渐从SQL演变为NoSQL。 因此,通常数据层也是缓存层。 主流的数据层分为几类:

l使用SQL数据库

在早期网络游戏中比较常见,尤其是日韩产品。 在使用亚马逊的服务时,基于LAMP的架构是最常见的。 很多产品只使用PHP+MYSQL,因此性能较低。 早期也有基于Socket Server+SQL的产品,比如使用Windows Socket Server访问MSSQL或者其他品牌数据库的产品。 据了解,韩国游戏大多属于这一类型。

由于性能问题,此类架构的游戏逻辑通常集中在客户端,很容易导致外挂盛行。 有些产品使用一些定制的数据层封装来提高性能,例如MySQL的Handler Socket Plugin。 还有一些直接使用NoSQL层作为缓存的产品,比如Memcached或者Tokyo Cabinet,应该会被广泛使用; 缓存后使用MySQL作为执行数据库。 这样做的主要目的是将数据层的操作与读写分离。 我们的产品使用类似的方法。 应用层访问自制的缓存,然后使用多进程读写进程将缓存连接到MySQL。

l使用NoSQL数据库

互联网应用的需求使得NoSQL迅速流行起来,游戏中也出现了很多NoSQL的应用。 常见的NoSQL仍然是主流的开源产品:Redis、MongoDB等。个人认为公司级产品,CKV和TCaplus都非常好用。 它们都是人们考虑已久的NoSQL数据层服务。 关于KM的资料很多,不再赘述。

数据层和缓存层在整个互联网中被广泛使用,这里不再赘述。 对数据层的要求也可以灵活构建,即可以形成独立的服务,也可以支持分布式服务。 缓存的建议是支持消除热量,支持持久文件的实施并促进数据导出和统计信息。

三、结论

回顾过去并盘点我自己的许多经验和摘要游戏角色,对后端的游戏肯定还不够。 游戏后端不仅包括开发模型和技术体系结构; 它更多地是关于游戏业务的抽象和设计,这需要了解游戏的特征以构建游戏的相应架构,还需要权衡游戏的后端操作方法对后端体系结构的影响,例如整个区域以及完整服务器的架构部署方法。

在这个竞争激烈的手机游戏时代,对这些游戏的需求变得越来越强劲。 我认为,在我们做出令人满意的摘要之前,需要对这些方面进行全面思考和解决。

文章来源:https://gwb.tencent.com/community/detail/100007