社交游戏之通用任务服务器设计与实践
【 导读】
本文主要介绍社交游戏的游戏引擎中,如何使用C/C++构建一个高效、通用性强、可扩展的任务服务器,通用的社交游戏设计方式是指前端使用Flash进行展示,后台使用PHP或其他语言进行逻辑控制,不需要关心数据的存放位置,以及数据与数据之间的关系,能可通过后台图形化界面配置与管理任务系统的数据和业务逻辑规则,从而减少游戏服务器端程序版本的发布,加速游戏研发速度和降低研发成本,提高游戏运营更新速度。
1. 整体框架
Figure.1是引入了任务服务器后的系统结构图,其中使用Nginx作为WEB服务器,在平衡扩展方面自然不在话下,这里一个特点就是使用了自定义的PHP扩展。有两个好处:
(1)纯C实现的PHP扩展插件中集成了网络IO,性能上相比原生PHP实现至少一个数量级的提升;
(2)PHP扩展插件与任务服务器之间使用的长连接机制,在进一步提升性能的同时,也起到了类似MMORPG框架中网关服务器的作用。
Figure.1
2. 服务器框架
Figure.2图表是第一版本的服务器框架,其基本思想是:
(1)任务定义的数据,存放在数据库单独的一张配置表中,服务器在启动时会预先载入这些配置数据,在服务器内存中生成一个索引表;
(2)玩家进行任务的动态数据是存放在服务器内存中的,以一定的机制定时刷新到数据库,如关键变更刷新,一定时间间隔定时刷新等;
(3)数据存储上进行分库分表设计,详情参考 mysqlops中相关分库分表文章;
(4)使用第三方Lua模块(SWIG,ToLua++等)或者自己实现一个Lua封装模块,这里使用自定义实现的Lua封装模块,同时对源代码(lua-5.1.4)进行了部分定制修改,以满足业务上的需求,这个留在后面细说;
(5)Lua脚本源代码只实现了部分由服务器引入的业务逻辑,这些逻辑可能会在后期维护升级时改动比较大,同时Lua源代码中没有任何的数据存储,只是调用服务器暴露出来的接口;
(6)这个框架在实际中遇到的问题有两个,一是需求的改动远远超乎想象,有时甚至一个功能升级就要改动大量的接口和逻辑,单靠暴露几个所谓“频繁改动”接口的“程序员猜想”方式,是无法满足策划人员的“大胃口”;二是任务定义存放在数据库当中,无形中增加了DBA的维护工作(改动极度频繁),并且在服务器方面还需要额外的信息配置;
Figure.2
针对上述(6)中的问题,于是就有了修改版的Figure.3框架,相对前一版本,有以下改动:
(1)增加了Lua Proxy模块,处理服务器和Lua脚本之间的互调交互;
(2)任务定义数据存放在Lua脚本中;
(3)Lua脚本负责任务全部的业务逻辑,并且任务动态数据也在Lua中进行维护;
(4)服务器中只保留极少的逻辑,主要都是为了性能提升和优化的角度考虑;
(5)这时问题又来了,Lua在进行reload时如何保证Lua中动态数据的完整性,稍后提供这个问题的解决方案;
Figure.3
3. 服务器基础流图
服务器主体分为 网络处理,逻辑线程和数据库处理三大部分,这里主要讲述逻辑线程的工作方式。
逻辑线程一般配置为4或者8,也可以根据CPU物理核数进行适当更改,网络线程处理好的完整命令包到达逻辑线程时,会以最简单的HASH(模取余)方式将其分配到相应的逻辑线程上进行处理,处理完的接口再异步投递到网络线程返回给前端,期间产生的数据变更,也会以异步方式更新到数据库中,如图Figure.4所示:
Figure.4
这里简单的以两个逻辑流程例子说明,任务的工作过程,如图Figure.5所示:
Figure.5
示例一,登录流程:
(1)玩家登录,发送登录命令到服务器;
(2)服务器为玩家分配逻辑线程;
(3)检查玩家不久(1小时)之前是否有过活动,没有则异步发送请求到数据库线程获取数据,此线程继续处理请他请求;
(4)数据库线程获取数据,异步回调到之前线程,使用数据获取的数据完成玩家数据的初始化(包括构建统计映射表),处理之前缓存的数据,完成一次玩家的登录操作;
(5)特别说明,此期间(3~4)该玩家的其他请求,选择重要的内容进行缓存,以免重要数据丢失,其他的则可以直接选择不处理;
示例二,统计行为:
(1)玩家请求,发送命令到服务器;
(2)服务器为玩家分配逻辑线程;
(3)玩家未登录,缓存请求数据;
(4)玩家已登录过,则查看映射表,检查状态机,做统计计数增加,判断任务状态变化,删除无用映射信息,返回信息给前端;
整体上来说,此服务器采用的是一种被动触发的方式,就是所有任务的状态变迁是依靠外来请求命令。所有任务包括一些时间相关的任务,都是请求到来时实时进行计算和状态变迁,而不是自身维护一系列的定时器,一方面是出去编码方便的考虑,另一方面也是这个足够满足业务的需求量。
3.1 任务之状态变迁图
服务器还实现了一个简单的有限状态机,用来处理任务各个状态之间繁琐的变化,大致状态图如Figure.6。这时一个集合很多类型的状态机,可以根据不同类型的任务进行相应的配置。
Figure.6
4. 团队任务的处理方式
由于之前用户时分布在各个逻辑线程的,而团队任务需要团队中玩家所有人的贡献,如果还是采用线程分布的方案,势必造成编码上复杂读的大幅度提升。
这里的解决方式很简单,就是直接用一个单独的线程处理所有团队任务的请求。从业务量的角度分析,团队任务请求是远小于个人任务的,因此这个解决方案不会造成服务器性能瓶颈。
5. 服务器承载能力
这里提供一个简单的测试数据:
CPU:物理4核逻辑8核
内存:12G
网卡:1000M
系统:CentOS 5.4
测试结果:
平均每个玩家,同时进行10个任务的情况下,可以支撑日活跃200W。其中,团队任务占10%比重,且内存中最多允许缓存20W玩家数据。
生产环境:日活跃80W,服务器LOAD值不大于2(备注:硬件配置略低于测试环境)。
6. 关于Lua
(1)定制目的
目前很大一部分的SNS平台ID长度都超过了32位,未能表示的最大整数的上限,而服务器又直接使用平台ID作为玩家ID进行索引,而Lua 5.1.4中Number默认为double类型,在表示大整型时会溢出,所以需要将Lua中Number定制成long整型。
(2)定制方法
废话不说,直接上代码,就改了两个文件中的几行代码,详细信息见代码截图Figure.7:
Figure.7
(3)带数据的Lua脚本如何reload,不多说看图Figure.8:
Figure.8
(4)多线中使用Lua
一句话,每个线程一个lua_State。
(5)注意事项
如果定制了lua源代码,则安装到目标服务器上则需要注意,将定制后的Lua编译的lua解释器也一并安装,否则会出现原生lua解释器无法解释Lua源代码脚本或者解释出不可预期结果。或者将改动后的lua-5.1.4源代码编译进服务器bin文件中,还好lua-5.1.4源代码和编译后都不大。简单的说,就是用定制编译,就要用用定制解释。
7. 不足与总结
(1)完全能实现游戏行业复杂的任务逻辑关系配置;
(2)任务配置数据的变更,不需要停机维护,也不影响正常的玩家;
(3)任务服务器的灾备HA功能待完善( 备注:可以借助HS模块改造);
(4)灾备HA基础之上,能否再实现分布式服务提供,需要大改造;
(5)生产环境的千万日活跃用户的考验,还未曾有机会经受过,可能会出现性能瓶颈;
【 编者加注】
当下的技术架构足够支持业务发展,并且可以超越业务发展1-2年的时间,待业务发展到更高要求的阶段,相信开发资源也会充裕点,至少不用整天忙于处理各种不同业务需求和大小事务,从而可以投入更多的资源进行完善这件非常有价值和艺术的改造,感谢 @ZEROV17的投稿支持,也欢迎各位技术朋友站内留言或者新浪微博直接交流!
原创文章,转载请注明: 文章地址 社交游戏之通用任务服务器设计与实践