手游优化

Aug 19, 2015


前言

萌仙最近在越南版上线了,在还没大推的情况下成绩还算是不错的,比国内版好很多。之所以能有这么好的成绩,离不开当初狠心的决定,从游戏本身和技术角度进行大面积的优化。

好的程序离不开50%的优化,另外的50%是代码开发阶段。萌仙在国内版的运营后期,经常有玩家反应很卡顿,更新时间长,手机发热严重等问题。我们在开始优化之前采用传统的性能统计找出相关的热点,着重于干掉热点。

玩家数据

萌仙后台采用的redis数据库。大部分情况下我们都是将玩家的数据以二进制块放进去的。而我们玩家的数据实际上是一个lua的table表。这种做法带来的好处是非常方便,不会跟C++有任何牵连,当然它带来的坏处也是很明显:玩家数据修改过于随意,一旦没有很好的监督问题就来了,还有就是随着玩家的成长,这个table表会非常大。我们的玩家数据大概是这样子的:

player = {};
player.acc = "dasdasda";
player.id = 550084;
player.data = {};
-- player.data下面就是各种游戏数据了

数据同步

萌仙里有一个数据同步机制,我们叫它cache。这个机制的作用以服务器推送的方式将数据下发至客户端。这个cache机制非常易用。它是自动同步的,也就是说只要你想要下发什么数据给客户端,只要在这个cache的table表下面加key-value就可以了,它会自动每帧下发到客户端。

这个机制带来的问题是:日积月累,写代码的人来来往往,于是这个cache里的东西非常非常多,导致每次下发给客户端的数据包非常大。问题来了,移动网络本身具有不稳定性,一次性下发这么大的数据是非常困难,一旦玩家收不到数据或者接收失败,就会出现玩家所说的卡顿。

我在服务端采集了每一帧的消耗,70%的时间都耗在了cache同步上,显然热点在这里。

解决方法:将cache干掉,客户端如果需要用到cache里的数据,那就随用随取,取到了之后就留在客户端,之后就采取差异同步的方式下发至客户端。

差异同步

萌仙之前是没有差异同步的,都是靠cache机制,这是不靠谱的,cache每次下发的数据包那么大。所以我们折腾了差异同步。但是由于萌仙在设计之初就虽然做了差异机制,但是整个方式压根就没实现。如果从头到尾实现差异对我们来说又非常耗时,所以我们选择了另外一种差异方式。

我们对player下面的data做了一个proxy,所以对player.data下的修改都会进入到这个proxy中,我们就在这个proxy里面去收集当时的差异存储在一个全局的table里。服务端每帧都会以玩家id找到这个玩家的数据差异,然后主动推送给客户端,由客户端处理差异的合并。

之所以选择proxy的玩家数据,是因为所有的玩家数据格式都是一致的,这也是项目已经运营了很久的情况迫不得已的选择。因为添加了proxy机制,将lua系统内的__index,__addindex,pairs,ipairs,remove,insert,sort等系统函数重载了,并修改了萌仙本身的同步机制。这种做法激进且复杂,一旦某个地方出错会影响服务端当前所加载的所有玩家。

这种采用proxy所有玩家的数据并在proxy收集差异的方式我个人并不赞同大面积大范围使用,过于复杂和繁琐,但是对萌仙而言,暂时没有找到比这个更好的方式。当然啦,在这块代码上死守代码质量还是可以接受的。

数据存储

萌仙后台采用的是Redis数据库,即key-value方式。我们将之与lua融合,在lua中可以直接在Redis中存入table。实现方式就是在存入db之前将table表pack成一个二进制数据包并进行压缩,在取出的时候将数据unpack然后解压即可得到原生的table表。

很多时候存入数据库的table表都非常大且有多层嵌套。Redis在存储数据的时候是以key来划分结点的,有如下的table数据:

data = {};
data.content = {};
data.global = {};
data.activity = {};
data.activity.RolePayActivity = {};
data.content.PureCardActivity = {};

假设在Redis中,data对应的存储key是ActivityData,那么在存储数据库时data将会被pack和压缩,一旦data很大,往往只是修改了某一个table里的字段,那么在存储的时候并不需要存储其他的table,只存储当前修改的table即可,否则上述的耗时将会增加,显然这里会形成热点。

解决方法是尽量避免直接存储大的数据块。在存储上改成:

ActivityData:content
ActivityData:global
ActivityData:activity:RolePayActivity
ActivityData:content:PureCardActivity

这样修改了RolePayActivity这个table里的东西时,只需要按照ActivityData:activity:RolePayActivity这个key值就可以将table表存储数据库了,大大的减小了pack和压缩的时间。

发热与耗电

我们为了加深游戏内容,对游戏内容做了很多修改和优化。随之带来了额外的问题,比如运行设备发热和耗电增加。刚开始我们是没有感觉的,后来我提交到testin上测试以及不少玩家的反应,才意识到发热与耗电的问题。

在testin的结果上看到,游戏在运行的GPU占比比较高,同时发热和耗电比较严重。

解决方法:在ios和android通过降帧减少draw的次数,由于萌仙是卡牌游戏,对帧数的要求不会那么高,所以降低到30帧之后效果还是可以接受,发热和耗电相对降低了许多。

客户端预加载资源

这个也没啥好说的,也没多大技术含量。在游戏启动的时候开一个线程去加载游戏中常用的比较大的资源图片。这些图片则可以通过配置动态的根据版本更新出去。

新更新机制的变更

由于之前萌仙的更新属于滚雪球的方式,更新包会随着时间的推移越来越大,到玩家的客户端里这个更新包的下载会很困难,玩家用的可是不稳定的移动网络。

所以忍受了一周的痛苦,将原来的更新机制废掉,重写了新的增量更新机制,并作了一个后台的dashbroad。关于更新机制会有另外一篇文章总结一下。

其实客户端的可优化点还有很多,但是也来不及一一去做,以后每做了一项优化就记录一下,就酱。