尽管分服的游戏模型已经运营了很多年,但是有一些游戏运营商还是希望能让尽量多的玩家一起玩。因为网游的人气越活跃,产生的交互越多,游戏的乐趣也可能越多。这一点最突出表现在棋牌类网游上。如联众、QQ游戏这类产品,无不是希望更多玩家能同时在线接入一个“大”服务器,从而找到可以一起玩的伙伴。在手游时代,由于手机本身在线时间不稳定,所以想要和朋友一起玩本来就比较困难,如果再以“服务器”划分区域,交互的乐趣就更少了,所以同样也呼唤这一个“大”服务器,能容纳下所有此款游戏的玩家。因此,开发者们在以前积累的分服模型和分线模型基础上,开发出满足海量在线互动需求的一系列游戏服务器模型——全服全线模型。
[图19-全服全线模型]
1、 服务进程的组织
a) 静态配置
全服全线模型的本质是一个各种不同功能进程组成的分布式系统,因此这些进程间的关系是在运维部署期间必须关注的信息。最简单的处理方法,就是预先规划出具体的进程数量、以及进程部署的物理位置,然后通过一套配置文件来描述这个规划的内容。对于每个进程,需要配置列明每个进程的pid文件位置;内部通讯用的地址,如IP+端口或者消息队列ID;启动和停止脚本路径;日志路径等等……由于有了一套这样的配置文件,我们还可以编写工具对所有的这些进程进行监控和操作批量启停。
[图20-静态配置]
虽然我们可以以静态配置为基础做很丰富的管理工具,但是这种做法还是有可以改进的空间:每次扩容、更换故障服务器或者搬迁服务器(这在运营中很常见),我们都必须手工修改静态配置数据,由于是人工操作,就总会产生很多错误,根据个人经验,游戏运营事故中的70%以上,是跟运维操作有关;由于整个分布式系统被切分成大量的进程,对于新进入此项目的程序员来说,要完整的理解这个系统,需要在思想上跨越层层阻隔:每个进程的功能、它们部署的关联、每个进程间的协议报的含义、每个业务流程具体的跨进程过程……这要花费很多时间才能搞明白的。而且大部分游戏的这种架构并不统一,每个游戏都可能需要重新理解一次,知识无法重用;在开发测试上,由于分布式系统的复杂性,要多搭几个开发、测试环境也是很费时间的,以至于这项工作甚至要安排专人来负责,这对于小型游戏开发团队来说几乎是不可承担的成本。因此我们还需要一些更加自动化,更加容易理解的全服全线游戏服务器模型。
b) 基于中心点的动态组织
SOA架构模式是业界一个比较经典的分布式软件架构模式,这个架构的特点是能动态的组织一个非常复杂的分布式服务系统。这个系统可以包含提供各种各样供的服务程序,而这些服务程序都以同一个标准接口来使用,并且服务自己会注册自己到集群中,以便请求方能找到自己。这种架构使用Web Serivce来作为服务接口标准,通过发布WSDL来提供接口API,这极大的降低了开发者对这些服务的使用成本。在游戏领域,服务器端提供的功能程序,实际上也是非常多样的,如果要构建一个分布式的系统,在这个方面是非常适合SOA架构的思想的;然而,游戏却很少使用HTTP协议及其之上的Web Service做通讯层,因为这个协议性能太低。不过,类似SOA的,基于中心节点的动态组织的服务管理思路,却依然适用。
[图21-基于中心点的动态组织]
一般来说我们会使用一组目录服务器来充当“中心点”,代表整个集群。开源产品中最好的产品就是ZooKeeper了。当然也有一些开发者自己编写这样的目录服务器。由于每个服务进程会自己上报负载和状态,所以每个进程只需要配置自己提供的服务即可:服务名字、服务接口。对于请求方来说,一般都可以预先编写目标服务接口的类库,用来编程,有些项目还使用RPC功能,使用IDL语言配置直接生成这些接口类库。当需要请求的时候,执行“名字查找”-“路由选择”-“发起请求”就可以完成整个过程。由于有“查找”-“路由”的过程,所以如果目标服务故障、或者新增了服务提供者,请求方就能自动获得这些信息,从而达到自动动态扩容或容灾的效果,这些都是无需专门去做配置的。
c) 服务化与云
尽管动态组织的架构有如此多优点,但是开发者还是需要自己部署和维护中心节点。对于一些常用的服务,如网络代理服务、数据存储服务,用户还是要自己去安装,以及想办法接入到这套体系中去。这对于开发、测试还是有一定的运维工作压力的。于是一些开发团队就把这类工作集中起来,预先部署一套大的集群中心系统,所有开发者都直接使用,而不是自己去安装部署,这就成为了服务化,或者云服务。
[图22-服务化、云]
使用专人维护的服务化集群确实是一个轻松愉快的过程。但是游戏开发和运营过程中,往往需要多套环境,如各个不同版本的测试环境、给不同运营平台搭建的环境、海外运营的环境等等……这些环境会大大增加维护服务化集群的工作量,对于解决这个问题,建立高度自动化运维的私有云,成为一个需要解决的问题放上了桌面。提高集群的运维效率,降低工作复杂程度,需要一些特别的技术,而虚拟化技术正式解决这些问题的最新突破。
2、 提高开发效率所用的结构
a) 使用RPC提高网络接口编写效率
在分布式系统中,如果所有的接口都需要自己定义数据协议报来做交互,这个网络编程的工作量将会非常的大,因为对于一个普通的通信接口来说,至少包括了:一个请求包结构、一个响应包结构、四段代码,包括请求响应包的编码和解码、一个接收数据做分发的代码分支、一个发送回应的调用。由于分布式的游戏服务器进程非常多,一个类似登录这样的操作,可能需要历经三、四个进程的合作处理,这就导致了接近十个数据结构的定义和无数段类似的代码。而这些代码,如果在单进程的环境下,仅仅只是三、四个函数定义而已。
因此很多开发者投入很大精力,让网络通信的编写过程,尽量简化成类似函数的编写一样。这就是前文所述的远程调用的方法。在全区全线的游戏中,如果是比较重度的游戏,采用RPC方式做开发,会大大降低开发的复杂程度。当然也有一些比较轻度的游戏,还是采用传统的协议包编解码、分发逻辑调用的做法。
b) 简化数据处理
在分布式系统中,对于避免单点、容灾、扩容中最复杂的问题,就是在内存中的数据。由于内存中有游戏业务的数据,所以一般我们不敢随便停止进程,也难以把一个进程的服务替换为另外一个进程。然而,游戏数据对比其他业务,还是非常有特点的:
l 写入越不频繁的数据,价值越高。比如过关、升级、获得重要装备。
l 大量数据都是读非常频繁,而写非常不频繁的,如玩家的等级、经验。
l 大量写入频繁的数据,实际上是不太重要,可以有一定损失,比如玩家位置,在某个关卡内的HP/MP等……
因此,只要我们能按数据的特性,对游戏中需要处理的数据做一定分类,就能很好的解决分布式中的这些问题。
l 首先我们要对数据的分布做规划,一般来说采用按玩家ID做分布,这样能让服务进程中内存的数据缓存高度命中。常用的手法有用一致性哈希来选择路由,调用相关的服务进程。
l 其次对于读频繁而写不频繁的数据,我们采用读缓存而写不缓存的策略。每个服务进程都保留其读缓存数据,如果需要扩容和容灾,仅仅需要修改服务访问的路由即可。
l 再次对于读不频繁而写频繁的数据,我们采用写缓存和读不缓存的策略。由于这些数据丢失掉一些是不要紧的,所以容灾处理就直接忽略即可,对于扩容,只需要对所有服务进程都做一次回写即可。
l 最后,有一些数据是读和写都频繁的数据,比如玩家位置,HP/MP这类,我们采用读写都缓存,由于数据重要性不高,只要我们多分几个服务进程即可降低故障时影响的范围;在扩容的时候调用全节点清理读缓存和回写脏数据即可。
在和持久化设备打交道的时候,传统的ORM类库往往能帮我们把数据存入关系型数据库,然而,使用一个自带数据热备的NOSQL也是很好的选择。因为这样能节省大量的分库分表逻辑代码。
c) 自动化部署集群环境
最新的虚拟化技术给分布式系统提供能更好的部署手段,以Docker为标志的虚拟化平台,可以很好的提高服务化集群的管理。我们可以把每个服务进程打包成一个映像文件,放入Docker虚拟机中运行,也可以把一组互相关联的服务进程打包运行。这些环境问题都由Docker处理了。
但是,我们同时需要注意的是,如果我们的进程的资源是静态分配的(前文提到),在Docker的虚拟机中可能因为内存不足等原因直接无法启动。这就需要我们把完全静态分配资源的程序,修改为有资源限制,但是动态分配的程序。这样我们才能在任何可以部署Docker的机器上部署我们的游戏服务器。
3、 分布式难点:状态同步
a) 分布式接入层
一般来说,我们全线服务器系统碰到的第一个问题,就是大量并发的网络请求。特别是大量玩家都在一起交互,产生了大量由于状态同步而需要广播的数据包。这些网络请求的处理,显然应该独立出来成为单独的进程。同时这些网络接入进程,还应该是一个集群中的成员。这就诞生了分布式接入服务层。
这些网路接入进程的第一个功能,就是把并发的连接,代理成为后端一个串行的连接,这可以让后端服务进程的处理逻辑更简单,而且网络处理消耗变得更小。
其次,网络接入进程需要支持广播功能。如果只是普通的广播实现,很多人会需要拷贝很多次需要广播的内容,然后挨个对Socket做发送。这其实是一个消耗很高的操作。而单独的网络接入进程,可以善用“零拷贝”等技术,大大降低广播的性能开销。而且还可以通过多个进程一起做广播操作,以达到更大的在线同步区域。
最后,网络接入进程需要支持一些额外的有用功能,包括通讯的加密、压缩、流量控制、过载保护等等。有些团队还把用户的登录鉴权也加入网络接入功能中。
[图23-分布式接入层]
b) 使用P2P
网络状态同步产生的广播请求中,绝大多数都是客户端之间的网络状态,因此我们在可以使用P2P的客户端之间,直接建立P2P的UDP数据连接,会比通过服务器转发降低非常多的负载。在一些如赛车、音乐、武打类型的著名游戏中,都有使用P2P技术。而接入进程天然的就是一个P2P撮合服务器。
有些游戏为了进一步降低延迟,还对所有的玩家状态,只同步输入动作,以及死亡、技能等重要状态,让怪物和一般状态通过计算获得,这样就更能节省玩家的带宽,提高及时性。加上一些动作预测技术,在客户端上能表现的非常流畅。
1、 可重用的游戏业务模版
游戏服务端的各种架构中,以前往往比较关注那些非功能性的需求:容灾性、扩容、承载量,延迟。而在现在手游时代,开发效率越来越重要,有些团队甚至不设专门的服务器端程序员。因此游戏服务端架构应该更多的关注业务开发的效率。
现代游戏中,只要是带RPG元素的,角色系统、物品系统、技能系统、任务系统就都会具备,而且都有一批比较稳定的核心逻辑。只要是能在线交互的,就有好友系统、邮件系统、聊天系统、公会系统等。另外商城系统、活动系统、公告系统更是每个游戏都似乎要重复发明的轮子。
游戏的后端应用也有很多可重用的部分,比如客服系统、数据统计平台、官网数据接口等等。这些在游戏服务端框架中往往是最后再添加进去的。
如果把以上的问题都统一考虑起来,我们实际上是可以在一个稳定的底层架构上,构造出一整套常用的游戏业务逻辑模板,用来减少游戏领域的业务代码开发。所以这样一套可以运行各种业务逻辑模版的底层架构,正是游戏服务端架构发展的方向。
2、 动态资源调度的PaaS云
现在有的团队已经在搭建自己的Docker云,这可以让游戏服务器在虚拟云上动态的生长,从而达到真正的动态扩容和动态容灾。加上如果游戏服务器不再是一个个服务进程,而是真正意义上的一个个服务,可以动态的加入或者离开云环境,那么这就是一个游戏领域的PaaS系统。我热切的希望能看到,可以用一套SDK,开发或重用那些成型的业务模版,然后动态注册到服务云中就能运行,这样一种游戏服务器架构。