SLG手游Java服务器的设计与开发——架构分析
微信公众号【黄小斜】大厂程序员,互联网行业新知,终身学习践行者。关注后回复「Java」、「Python」、「C++」、「大数据」、「机器学习」、「算法」、「AI」、「Android」、「前端」、「iOS」、「考研」、「BAT」、「校招」、「笔试」、「面试」、「面经」、「计算机基础」、「LeetCode」 等关键字可以获取对应的免费学习资料。
文章版权归腾讯GAD所有,禁止匿名转载;禁止商业使用;禁止个人使用。
一、前言
从去年12月份开始,到现在,我全程参与了公司一款SLG手游的研发,负责整个游戏的服务端部分。这也是我第一次单独负责一款网游的服务端开发,整个研发过程,也让我的各方面技术提升了不少。目前这款游戏正在紧张的测试中,预计下周左右会在XY渠道进行一轮封测,以测试玩家对我们的游戏的反馈数据。
这款游戏集合市面上大多数SLG的特点,包含了卡牌、装备、科技、建筑的等养成内容,同时也在战斗系统做了创新,通过卡牌搭配,装备搭配,以及阵型搭配,再加上各方面的养成数据,能让玩家在战斗中产生不同的效果。同时,他最大的特点也是最核心的部分,就是国战,不过很遗憾的是,第一版上线不会有国战内容,因为在测试过程中,我们发现整个国战的游戏机制还存在很多漏洞,我们将会持续优化,优化出一版完整的国战内容,再呈现给玩家。
我经历了整个开发过程,在游戏服务器开发方面有了不少新的认识,我想通过写文章的方式把这些设计思路、技术难点、以及踩过的坑,都分享给大家,所以我们准备通过系列文章的方式一点一点把服务器的框架内容分享出来,本章首先从游戏服务器的架构分析开始。
二、功能介绍
在开始设计整个游戏服务器的架构之前,我首先需要对游戏的功能有所掌握。上文已经简单提到了部分核心功能,比如卡牌、装备、阵型等,但实际上游戏的内容并不止这些。这款游戏属于SLG手游,即策略类的游戏,游戏以三国为题材,通过获得三国名将卡牌,搭配各种装备,设置战斗阵型,达到在战斗中的不同表现效果,战斗模式类似于《小小军团》的战斗,但是小小军团主要以兵种为核心,而我们的战斗以武将为核心,不同武将有不同的技能,不同的战斗属性,合理搭配,才能发挥最大的效果。围绕着这些游戏核心,设计了以下不同的功能模块。
1.账号系统
作为一款网游,最基础的,就是账号系统,一款网游一定会有账号系统,这是保证玩家能从不同终端都能进入自己的游戏数据的凭证。不过最近在手游市场,一般情况游戏都不会有自己的账号系统,而是接入第三方的账号系统,比如腾讯游戏一般会让玩家选择QQ登录或微信登录,网易一般会让玩家选择网易账号登录,还有很多渠道如XY,海马玩,快用等,都有自己的账号系统,账号系统的功能,就是保证玩家的唯一标识。
2.君主模块
进入游戏之后,玩家就需要创建一个君主,并设置君主的名字,性别,头像和国家。SLG游戏中,资源也是一个很重要的内容,所有的资源都是跟玩家直接相关的,所以资源内容也是跟君主相关,我们就可以把资源内容绑定在君主模块中,玩家在游戏中,是扮演这个君主的角色,所有游戏中的资源消耗以及资源产出,都是跟君主息息相关。在竞技场以及国战等pvp玩法中,君主也是玩家在游戏的唯一身份显示。
3.卡牌模块
这款游戏在SLG的基础之上加上了卡牌养成玩法,玩家需要通过收集卡牌来进行游戏的操作,卡牌包含了吕布、关羽、张飞、华雄等三国名将。卡牌分了白绿蓝紫橙红的品质分段,不同品质的卡牌有着不同的属性数值。卡牌可以通过酒馆抽卡获得,抽卡分为单次抽卡和十连抽,十连抽会必中一张橙色卡片。卡牌具有星级属性,多张相同的卡牌还可以进行升星操作,升星具有一定的失败几率,品质越高,失败率越高。如果有多余的卡牌,还可以进行分解操作,分解卡牌可以获得将魂和银两,而将魂还可以继续用于抽卡。在战斗中,上阵的卡牌将会增长经验,而卡牌的等级,就与它的经验相关。总结起来,卡牌具有等级和星级两个养成属性,其它还有统勇智以及攻击防御,技能伤害,普攻伤害等基础属性。整个卡牌模块还有很多养成点的。
4.装备模块
有了战斗的卡牌,那就必须给卡牌配备装备模块了,所有的卡牌具有6个装备位,每个装备位对应一种装备,装备也分为了白绿蓝紫橙红的几个品质阶段,不同品质的卡牌也能给卡牌加上不同的属性效果,卡牌同样具有等级和星级两个养成属性以及其他的基础属性,装备可以进行强化以强化等级,以及升星来提升装备的星级,升星消耗任意的装备作为材料。卡牌模块配合装备模块,可以有更多的养成玩法的自由发挥空间。
5.道具模块
游戏中也有常见的背包,背包中存放着各种道具,道具种类繁多,包括粮草、银两、经验丹等资源道具,也可以在背包中查看拥有的卡牌和装备。背包中可以直接使用道具,使用完道具就能增加相应的资源。
6.建筑模块
玩家刚进入游戏会看到很多建筑,这些建筑都具有不同的功能,升级这些建筑会增强建筑的属性效果,建筑包括皇城、军机处、校场、仓库、招商局、兵营、酒馆、竞技场、铁匠铺、农田(6个)、民居(6个),各个建筑具有不同的功能,皇城等级限制所有建筑的最高等级,军机处可以解锁升级科技,军机处等级限制科技等级,仓库等级限制最高资源上限,招商局等级限制民居产出,兵营等级限制兵力上限,铁匠铺等级限制装备强化等级,农田和民居的等级限制资源的产出,竞技场和酒馆没有等级,竞技场是功能入口,酒馆包含抽卡,卡牌升星和卡牌分解的功能。
7.科技模块
军机处的科技收到军机处建筑的等级限制,科技一共有30个,随着军机处的等级分别开放,不同的科技能增加不同的效果,其实有部分科技是属于阵型,每解锁一种阵型,就能在战斗前设置战斗的阵型,阵型中的位置也会随着君主的等级而开放。
8.关卡模块
游戏核心内容是战斗,战斗包含了普通关卡和精英关卡、以及竞技场、国战和日常副本。其中,普通关卡、精英关卡和日常副本在功能上来说是差不多的,只是战斗中的数值配置不同,精英关卡和日常副本加了挑战次数限制,日常副本还加了难度解锁限制。
9.竞技场
玩家的君主等级升级到16级时会开启竞技场,竞技场是玩家第一次感觉自己玩的不是单机的内容,竞技场的规则是打离线数据,服务器开服之后会初始化2000名机器人,玩家选择挑战前面排名的玩家,如果挑战成功,则互换位次,如果挑战失败,则位次不变,同时成功失败均会损失挑战次数,挑战次数次日刷新。
10.活动模块
国产游戏最少不了的还是活动模块了,做游戏除了卖情怀,就是卖活动,活动是一款游戏付费率最高的内容。这款游戏中包含的活动不多,只有每日签到、会员、首冲、以及豪华签到。每日签到是每9天一个轮回,每天签到送不同的奖励,其他几种活动均是需要充值人民币之后再领取到相应的奖励。
11.国战模块
国战分为两个模块,国战PVE和人国战PVP,玩家30级开启国战之后会进入一个大地图,地图上有76座城池,玩家首先需要进入国战PVE模块,把所有的城池都攻打下来才会开启国战的PVP模块,不过很遗憾的是,即将上线的版本的国战PVP模块并没有开放,打完PVE之后就会弹出提示PVP暂未开放。虽然没有开放,但实际上我们也做了一版国战,不过在测试中发现其机制有问题,便决定优化之后再上线这个模块。国战PVE同关卡部分大同小异,只不过由闯关改为了攻打城池。
12.热更新
现在的网游大部分都会有热更新的功能,所谓热更新,就是指用户在不从应用商店更新游戏版本的情况下,直接进入游戏,在游戏中更新游戏版本。热更新的原理就是在服务端存储一个游戏版本,客户端每次进入游戏先读取服务端的版本信息,如果有最新版本,就直接从服务端进行下载,下载完在本地解压更新本地资源,然后再进入游戏,这样玩家就能看到最新版本的游戏资源。
以上是部分功能内容,其他还有很多边缘的系统功能没有介绍,通过这部分内容我们就可以开始对游戏的服务器框架进行分析设计了。
三、网络通信
通过功能设计,可以发现,除了国战模块,游戏整体对实时性要求都不高,与前端商议之后,决定先使用HTTP短连接,国战部分再做商议(但其实后来开发国战模块也是通过短连接实现的,因为其机制就是一个异步的机制),网络通信的数据传输也暂时采用最简单的JSON字符串。不过目前这个机制确实产生了很多问题,首先是HTTP协议,在其他模块没多大问题,但在国战模块的时候要求实时性较高,应该采用实时性更强的TCP长连接比较合适。另外就是数据传输的问题,直接采用JSON字符串确实也没问题,但是传输字符串会让传输信息量变大,这样在弱网情况下会使得游戏的体验很不好,如果采用二进制进行传输,就会好很多。这次的这方面问题没有考虑周全,下一次我还是希望采用TCP长连接加Google Protobuffer这样的二进制传输协议进行数据交互。
四、数据存储
游戏数据分为两部分:游戏数据和玩家数据,游戏数据指的是游戏中的静态数据,如果签到奖励,战斗掉落,抽卡概率等内容,这部分内容均由策划配置好静态表,服务器启动时直接读取静态表,将表中内容加载到服务器内存,使用时直接从内存读取,而玩家数据指是随着玩家的操作而变化的数据,这部分数据我又分为了冷数据和热数据,热数据即游戏中读写频繁的数据,如免费抽卡次数、君主体力恢复、国战城池状态等,这部分数据的特点就是读写都很频繁,并且数据结构各不相同,每时每刻这些数据都有可能产生变化,产生读写操作,这部分数据,数据结构各不相同,可以采用nosql数据库,而Redis不仅是nosql数据库,还是内存数据库,非常适合这部分数据的存储。冷数据,则指的是交互相对不是很频繁的数据,如君主等级,卡牌等级,卡牌星级,装备等级,装备星级等,这部分数据的特点就是读写不是很频繁,需要玩家做了一定操作才会发生变化,并且数据具有结构化的特点,这些数据可以抽象出关系型数据表,所以我采用了Mysql进行了这部分数据的存储,为了考虑数据库性能,我采用了Memcache进行Mysql结果集的缓存。后来我认为,其实游戏数据的结构应该是采用Mongo更符合需求,它多变的数据结构满足游戏中的各种数据类型,但由于确实Mongo的实际开发经验和维护经验,也没敢轻易尝试。
五、架构设计
作为后端服务器,能否对前端请求快速做出响应,是判断一个服务器性能是否良好的重要指标,对于客户端来说,服务端就是一个url地址或者一个套接字,但是对于整个服务端架构来说,暴露给客户端的或许只是其中的一个连接处理服务器。良好的服务端架构是整个服务端开发成功了一半。我个人经验并不是很丰富,所以我所设计的架构也并不是很好的架构,大多数都是我参考了网上大量的关于服务端架构的文章之后的设计,在参考别人文章的过程中,自己也有了很多感悟。
根据游戏的需求,可将服务器大致分为登录服务器、逻辑服务器、文件服务器、支付服务器、国战服务器和聊天服务器。在第一轮进行封测时候只有一台服务器,所以目前所有的服务器设计均部署在同一台服务器上,提前设计好架构以便在以后更多玩家进来之后进行服务器分离,以承受更多的负载。
总体的结构设计图如下:
1.登录服务器
负责接入客户端的登录,选服的功能,多台登录服务器可通过Nginx配置负载均衡以承载更多玩家的连接。玩家登录选服之后,登录服务器会返回逻辑服务器的地址,此时客户端与登录服务器便没有任何关系,只需要拿着地址去连接相应的逻辑服务器,通过引导玩家去到不同的游戏服就实现了玩家流量的分流。
2.逻辑服务器
这是游戏中最重要的用于处理玩家游戏逻辑的服务器,玩家的所有逻辑操作都将基于此服务器,如果要用到其他的服务,则采用Rpc的通信方式调用其他服务器的进程,逻辑服务器与其他服务器的通信采用Motan Rpc框架,逻辑服务器作为Motan的调用方,其他服务器均作为Motan的服务方。
3.文件服务器
主要用于游戏中的热更新,进入游戏前,客户端将进行版本检查,如果发现有最新版本的内容,会提供文件服务器的最新版本下载地址,客户端请求文件服务器进行更新版本文件下载。
4.支付服务器
游戏中的充值付费均由支付服务器完成,在逻辑服务器调用支付操作之后,逻辑服务器会通过Motan Rpc调用支付服务器发起支付操作,之后支付服务器会开始调用相应的支付操作,目前游戏的支付一般是众多平台的联运,会接入第三方的支付SDK,各联运商的支付接口规范各式各样,开发商必须要遵循个平台的规范。
5.国战服务器
国战服务器负责处理游戏中的核心玩法——国战玩法,由于国战的同时在线人数可能较多,所以我把此部分单独分为一个服务器,来处理国战部分的内容,玩家通过逻辑服进入国战服务器之后,所有的接入将会转为国战服务器,由国战服务器来处理国战相关的内容,这样又能把一部分玩家流量分出来,承担更多的负载。
6.聊天服务器
游戏暂无聊天功能,但如果以后加入聊天功能,会将此功能单独分出来作为聊天服务器,处理游戏中的聊天信息。
六、系统架构
以上对服务器的部署架构进行了设计,以上设计按照功能划分,把游戏服务器分为了多个模块,以逻辑服为中心,其他服为服务提供者的方式进行的服务器划分。各个模块在承受不住玩家压力时都可以再纵向做服务器的集群扩展,个人感觉这种设计还是比较合理的。由上可见,逻辑服务器是游戏服务器整个架构的核心。
架构设计之后就要开始对服务器中的技术进行选型,首先开发语言是定位了Java,那么从网络层来说,常见的就有Servlet、Spring、Struts、Netty、和Mina,Servlet、Spring和Struts其实可以归为一类,因为Spring和Struts实际上就是对Servlet的一层封装,在Java Web开发来说,应该说已经是很成熟的技术了,他们其实已经对底层的链路做了良好的封装,仅支持HTTP协议,用户使用他们只需要关注核心的业务逻辑即可,但在Servlet3.0以前,Servlet的IO都是同步阻塞的IO处理(BIO),从3.0开始,才将Servlet API和NIO结合在了一起,在游戏中,玩家客户端与游戏服务端的请求处理操作是非常频繁的,从IO方式来看,显然异步的NIO机制要比同步的BIO快很多,基于NIO能构建出IO处理速度更快的服务端,而Mina和Netty都是基于NIO的网络框架。最终,在Servlet3和Netty,以及Mina中,我选择了Netty作为我的网络层框架,原因是Netty有更多的成熟案例,API开发更加简易,并且有更多的社区和资料。
从数据层来说,前文已经提到了,我将使用Mysql来存储玩家冷数据,用Redis存储玩家热数据,使用Mysql时结合Memcache缓存查询结果集,增加数据库的读性能。使用Redis直接使用Redis官方的Jedis API即可,Jedis不仅能直接连接Redis,也能用Sentinel进行Redis的主从集群。Mysql我使用了Hibernate做数据库ORM框架,原因是游戏中不会有太多的复杂查询,最多会有一个类似于"where userid=1"这样的查询条件,没有太复杂的SQL语句,Hibernate对JDBC做了良好的封装,如果没有很多复杂的SQL语句,则可以直接使用Hibernate即可。虽然Hibernate的性能不如MyBatis或JDBC,但有了Hibernate+Memcache的方案,相信能弥补一些性能上的不足。
还有其他一些技术就不再做过多的介绍,总体的架构流程如下:
1.游戏客户端为Cocos2d,与服务器交互采用Http通信,数据传输采用Json格式字符串
2.服务器端的网络层使用基于Netty实现的Http服务器
3.通过Netty接入客户端请求,根据请求数据中的协议号,调用服务器中相对应的逻辑模块
4.逻辑模块处理消息,若要处理游戏数据则调用Jedis或Hibernate处理,若触发某事件,则调用事件处理器
5.通过Netty的ChannelHandlerContext返回处理结果
6.客户端与服务器交互的数据通过XXTea+Base64进行加密处理
系统架构图如下:
七、总结
至此,本文对游戏的架构分析的内容就结束了,我参加工作也不久,个人经验很欠缺,文中描述的技术若有误导的部分,还请帮忙指出来,不要继续误导其他人,再次感谢大家。本系列文章是我本人参与一款SLG手游服务端开发的一些个人见解,我只是想把我学到的,我知道的东西分享给大家。下章开始,我将从各个部分,以源代码为基础进行详细介绍,下章先讲讲Netty在HTTP游戏服务器中的应用。
八、后续
从产品立项到开发,到测试,我经历了整个开发的过程,整个过程除了让我在个人技术上有了不少提升、对游戏服务器有了新的认识之外,也让我对整个游戏行业有了很多的看法。在产品的研发过程中,我们团队见证了COK的火爆,CR的兴起,ChinaJoy中也看到了中国有很多优秀的手游作品,同时我们也看到了国家在7月1日开始对游戏行业立的新规,以及苹果对中国政策的妥协。我们目前的状态,可以说是挑战与机遇并存,小型游戏CP团队,不是生,就是死。在这大半年开发中,我们的产品也是在不断调整方向,以适应残酷的游戏市场,国家出了新规之后,我们也是第一时间就去申请了文网文以及游戏版号,足以见得,我们团队的每个人,都想要在这场无声的战争中活下来。眼看产品就要上线,我们也对自己这款产品做了上线后的数据目标以及盈利目标,不管怎么样,我认为这是我们辛勤付出的东西,不管成功与否,我们都有了宝贵的经验,也算对得起努力的付出了。