游戏后台服务技术选型
一、游戏服务器
1.1、网游服务器的三种模型
之前的网游服务器都是分区分服,玩家都被划分在不同的服务器上,每台服务器运行的逻辑相同,玩家不能在不同服务器之间交互。想要更多的玩家在同一世界,保持玩家的活跃度,于是就有了世界服模型了。世界服类型也有以下3种演化:
一类型(三层架构)
网关部分分离成单端的gate服务器,DB部分分离为DB服务器,把网络功能单独提取出来,让用户统一去连接一个网关服务器,再有网关服务器转发数据到后端游戏服务器。而游戏服务器之间数据交换也统一连接到网管进行交换。所有有DB交互的,都连接到DB服务器来代理处理。
二类型(cluster)
有了一类型的经验,后续肯定是拆分的越细,性能越好,就类似现在微服务,每个相同的模块分布到一台服务器处理,多组服务器集群共同组成一个游戏服务端。一般地,我们可以将一个组内的服务器简单地分成两类:场景相关的(如:行走、战斗等)以及场景不相关的(如:公会聊天、不受区域限制的贸易等)。经常可以见到的一种方案是:gate服务器、场景服务器、非场景服务器、聊天管理器、AI服务器以及数据库代理服务器。如下模型:
以上中我们简单的讲下常见服务器的三种类型功能:
场景服务器:它负责完成主要的游戏逻辑,这些逻辑包括:角色在游戏场景中的进入与退出、角色的行走与跑动、角色战斗(包括打怪)、任务的认领等。场景服务器设计的好坏是整个游戏世界服务器性能差异的主要体现,它的设计难度不仅仅在于通信模型方面,更主要的是整个服务器的体系架构和同步机制的设计。
非场景服务器:它主要负责完成与游戏场景不相关的游戏逻辑,这些逻辑不依靠游戏的地图系统也能正常进行,比如公会聊天或世界聊天,之所以把它从场景服务器中独立出来,是为了节省场景服务器的CPU和带宽资源,让场景服务器能够尽可能快地处理那些对游戏流畅性影响较大的游戏逻辑。
网关服务器: 在类型一种的架构中,玩家在多个地图跳转或者场景切换的时候采用跳转的模式,以此进行跳转不同的服务器。还有一种方式是把这些服务器的节点都通过网关服务器管理,玩家和网关服务器交互,每个场景或者服务器切换的时候,也有网关服务器统一来交换数据,如此玩家操作会比较流畅。
通过这种类型服务器架构,因为压力分散了,性能会有明显提升,负载也更大了,包括目前一些大型的 MMORPG游戏就是采用此架构。不过每增加一级服务器,状态机复杂度可能会翻倍,导致研发和找bug的成本上升,这个对开发组挑战比较大,没有经验,很容出错。
三类型(无缝地图)
魔兽世界的中无缝地图,想必大家印象深刻,整个世界的移动没有像以往的游戏一样,在切换场景的时候需要loading等待,而是直接行走过去,体验流畅。
现在的游戏大地图采用无缝地图多数采用的是9宫格的样式来处理,由于地图没有魔兽世纪那么大,所以采用单台服务器多进程处理即可,不过类似魔兽世界这种大世界地图,必须考虑2个问题:
1、多个地图节点如何无缝拼接,特别是当地图节点比较多的时候,如何保证无缝拼接
2、如何支持动态分布,有些区域人多,有些区域人少,保证服务器资源利用的最大化
为了解决这个问题,比较以往按照地图来切割游戏而言,无缝世界并不存在一块地图上面的人有且只由一台服务器处理了,此时需要一组服务器来处理,每台 Node服务器用来管理一块地图区域,由 NodeMaster(NM)来为他们提供总体管理。更高层次的 World则提供大陆级别的管理服务。
一个 Node所负责的区域,地理上没必要连接在一起,可以统一交给一个Node去管理,而这些区块在地理上并没有联系在一起的必要性。一个 Node到底管理哪些区块,可以根据游戏实时运行的负载情况,定时维护的时候进行更改 NodeMaster 上面的配置。
对象的无缝迁移
玩家A、B、C分别代表3种不同的状态,以及不同的迁移方式,我们分别来看。
- 玩家A: 玩家A在node1地图服务器上,由node1控制,如果迁移到node2上,需要将其数据复制到node2上,然后从node1移除。
- 玩家B: 玩家B在node1和node2中间,此时由node1和node2维护,若是从node1行走到node2的过程中,会向1请求,同时向2请求,待全部移动过去了再移除。
- 玩家C:玩家C在node2地图服务器上,由node2控制,如果迁移到node1上,需要将其数据复制到node1上,然后从node2移除。
具体魔兽世界服务器的分析,篇幅过多,我们以后再聊。
1.2、房间服务器(游戏大厅)
房间类玩法和MMORPG有很大的不同,在于其在线广播单元的不确定性和广播数量很小。而且需要匹配一台房间服务器让少数人进入一个服务器。
这一类游戏最重要的是其“游戏大厅”的承载量,每个“游戏房间”受逻辑所限,需要维持和广播的玩家数据是有限的,但是“游戏大厅”需要维持相当高的在线用户数,所以一般来说,这种游戏还是需要做“分服”的。典型的游戏就是《英雄联盟》这一类游戏了。而“游戏大厅”里面最有挑战性的任务,就是“自动匹配”玩家进入一个“游戏房间”,这需要对所有在线玩家做搜索和过滤。
玩家先登录“大厅服务器”,然后选择组队游戏的功能,服务器会通知参与的所有游戏客户端,新开一条连接到房间服务器上,这样所有参与的用户就能在房间服务器里进行游戏交互了。
二、游戏服务器架构要素
对于游戏服务端架构,最重要的三个部分就是,如何使用CPU、内存、网卡的设计:
内存架构:主要决定服务器如何使用内存,以最大化利用服务器端内存来提高承载量,降低服务延迟。
逻辑架构:设计如何使用进程、线程、协程这些对于CPU调度的方案。选择同步、异步等不同的编程模型,以提高服务器的稳定性和承载量。可以分区分服,也可以采用世界服的方式,将相同功能模块划分到不同的服务器来处理。
通信模式:决定使用何种方式通讯。基于游戏类型不同采用不同的通信模式,比如http,tcp,udp等。
2.1、通信模式
1、网络接入层
网络接入层的主要任务是建立客户端和后台服务以及客户端之间的信道,接收来自客户端大量并发请求,考核该层的主要性能指标是:高吞吐、低延迟。因而网络接入层开发考验的是开发者高性能网络编程的功底,即解决C10K甚至C10M的能力。
1.1协议选择
根据OSI的七层网络参考模型,我们可将网游网络也做如下7层划分:
其中4层以下都由操作系统来负责,开发者无需为此操心,在实际的开发过程中开发者首要面临的问题便是传输层是采用TCP还是UDP,下表简要对比了两者的优劣。综合两者优劣,简单来说除非对延迟有极致要求(例如FPS、MOBA类游戏)需采用UDP外,TCP可应对大部分游戏。在实际游戏开发中不管是采用TCP还是UDP方式,都很少直接通过 Socket编程方式来进行,一来因为开发工作量大,质量性能难以保证;二来平台兼容性不好(比如H5并没有提供socket编程能力),而是基于更上层的通讯协议比如基于TCP的HTTP、Websocket协议,GRPC,以及基于UDP实现的QUIC,WebRTC协议等。
值得注意的是基于安全性考虑,浏览器标准未提供UDP收发能力,QUIC协议也只在chrome得到了支持,WebRTC也还不是浏览器事实标准且协议初始目的是用于实现点对点的音视频通信,协议内容过于庞杂不容易提炼应用于游戏开发中,因而现阶段H5游戏还只能采用HTTP或Websocket方式通讯。
通讯协议确定后,随后要考虑的便是游戏对象的序列化,序列化主要有基于文本、基于二进制两种,其优劣如下表所示。在开发过程中一般会先采用文本序列化方式,便于前后端开发联调,在游戏正式上线前切换至二进制序列化方式以减少传输流量、提升编解码效率。
至于数据安全性问题,为了保护敏感数据安全开发者可以选择安全的https或WSS通讯协议,而对于直接基于TCP协议通讯,可采用先用RSA协商加密秘钥,然后使用对称加密方式将数据加密后发送。
通过以上分析,对于游戏协议类型的选择我们给出有以下准则:
1、弱联网类游戏:诸如休闲、卡牌类游戏可直接HTTP协议,对安全性有要求的话就使用HTTPS;
2、实时性,交互性要求较高:这类游戏一般需要保持长连接,优先选择标准的ws协议(同时使用二进制序列化方式),如考虑安全性可使用wss协议。而对于提供socket接口的native平台也可使用TCP协议,同时对数据做对称加密增强安全性;
3、实时性要求极高:不仅需要和服务器保持长连接,且延迟和网络抖动都要求极高(如FPS,赛车类游戏),可使用基于UDP的实现流传输协议如QUIC,KCP等。
1.2并发模型
为了处理来自客户端的并发请求,服务端有4种常见的并发模型。
1.2.1进程
进程是最早采用的并发模型,进程作为操作资源分配、调度的单位,拥有独立的运行空间。进程并发模型中每个请求由独立的进程来处理,进程一次只能处理一个请求,该模型最大的优点就是简单。如果处理请求的进程由于系统调用而阻塞或进程的时间片用完,抢占式的进程调度器就会暂停旧进程执行,调度执行新的进程,这个过程涉及大开销的上下文切换,进程并发模型的缺点是比较低效。最典型的采用进程模型的服务有Apache。
1.2.2线程
线程并发模型是进程模型的改进,线程从属于进程,是系统更小粒度的执行调度单元。不同请求可由进程内多个并发执行的线程来处理,这些线程由操作系统内核自动调度。线程相对进程的主要优势在于,调度上下文切换开销更小,但由于多个线程共享地址空间,需要额外的线程间互斥、同步机制来保证程序性正确性。典型的采用线程模型的服务有Tomcat。
1.2.3 IO多路复用
利用操作系统提供的epoll等IO多路复用机制,能同时监控多个连接上读、写事件, IO多路复用也称事件驱动模型,网络程序执行逻辑可抽象为事件驱动的状态机。 IO多路复用避免了读写阻塞,减少了上下文切换,提升了CPU利用率和系统吞吐率。但IO多路复用它将原本“同步”、线性的处理逻辑变成事件驱动的状态机,处理逻辑分散于大量的事件回调函数。这种异步、非线性的模型,极大地增加了编程难度,如nodeJs的常见的回调地狱问题。典型的采用IO复用模型的服务有Nginx,netty。
1.2.4 协程
协程也称为轻量级线程,是一种协同的、非抢占式的多任务并发模型。 协程运行在用户空间,当遇到阻塞或特定入口时,通过显式调用切换方法主动让出CPU,由任务调度器选取另一个协程执行。
协程切换只是简单地改变执行函数栈,不涉及内核态与用户态转化,也涉及上下文切换,开销远小于进程/线程切换。协程的概念虽早已提出,随着近些年年越来越多的语言(go、 Haskell)内置对协程支持才被开发者所熟知,协程极大的优化了开发者编程体验,在同步、顺序编程风格能快速实现程序逻辑,还拥有IO多路复用异步编程的性能。典型的采用协程模型的服务有openresty(Lua), gevent(Python), golang。
以上总结了目前4种常用的并发模型,它们在工作原理、运行效率、编程难度等方面有显著区别,各自有适用场景,在实际使用时应该根据需求仔细评估。在实际开发过程中如果没有可复用的现成网络组件或历史包袱我们建议使用协程并发模式开发网络接入层服务。
参考:https://www.cnblogs.com/hwcs/p/7203605.html
参考:https://blog.csdn.net/matchvs/article/details/80053968