对英雄联盟手游的一些技术浅析
最近英雄联盟手游(LOLM)可以玩了,充满好奇的我就简单分析了一些体验到的技术点。
英雄联盟手游(LOLM)客户端是基于Unity研发。网络同步方案是帧同步。
——————————————————————————–
帧同步快速恢复。
现象:游戏后台60秒,回到前台花了14秒快速运算直最新。游戏内体验如下视频1
视频1
注:视频1没有人为加速处理,视频1是开启iOS系统的录像功能后,回到游戏发生的事,录像中能够很明显看到整个游戏世界被加速了。
分析: 客户端跑60帧的话,单帧消耗是3.8毫秒(14/60*16.6)。恢复过程能够看到整个游戏世界都在加速,包括显示相关。
单看这个单帧消耗数据,假设游戏进行了20分钟,那么恢复过程就有4分钟,这么长的时间,体验会很不好。
为了解决体验问题有以下解决方案的猜测:
猜测1,恢复的时候,在一帧需要处理的运算中,可以把不必要的处理流程省略掉(比如渲染)以减小恢复需要的计算量,这样就可以加快恢复速度。
猜测2,战斗进行过程中,可以在服务端同步运算游戏逻辑(客户端在一帧一帧的模拟游戏逻辑,服务端不只是在广播帧操作给客户端,服务端也在一帧一帧的模拟游戏逻辑。
传统的帧同步服务端大部分只是在广播玩家们的帧操作给客户端),支持服务端生成一帧的快照,把快照同步给客户端,客户端再根据快照来恢复,从而避免较长的帧计算过程。
帧计算过程对于恢复来说会产生大量无用的信息,浪费性能。
———————————————————————————–
完全断线重连。
现象:在游戏10分钟的时候,杀掉游戏客户端进程,再重新打开游戏客户端从开始显示战斗loading界面,到能玩,花了大概5秒。
分析1:如果是帧同步快速恢复的猜测1的方式达到快速恢复目的。 假设这5秒有4秒在跑逻辑运算,其中1秒是消耗在收网络消息。
当然资源模型加载可以放在多线程里面计算,因此这5秒暂不考虑这些开销了。
假设逻辑是跑20帧,10分钟就有1.2W帧要运算,这4秒平摊下来单帧逻辑运算时间开销是0.41ms(测试设备是iPhone XS Max)。
对于这么牛逼的数据在设计整体思路上有以下猜测:
猜测1,如果是重Lua的开发模式,要达到这个性能指标比人类上火星还难,猜测是C#实现绝大部分战斗逻辑。不过就算是C#,0.41ms这个数据也是特别特别牛逼了。
猜测2,如果CPU是8核,恢复过程都利用起来了的话一帧的总运算量是3.28ms,这个数据看起来还很比较符合我的历史开发经验。如果要把8核很好的利用起来,并且可工程化的方案,我第一反应就是ECS(面向数据编程)的开发框架。
对于快照恢复,最难的处理部分还是要在客户端100%还原出数据,哪怕是元素在vector中的顺序都不能变。
分析2:如果是上面提到的[帧同步快速恢复的猜测2]的方式达到快速恢复目的。
如果服务器下发了一帧的快照,客户端依据快照来进行恢复,这5秒还是绰绰有余的。
如果是这个方案,那么该方案的实践有以下几个点:
点1.因为服务端要跑游戏逻辑,并且和客户端一起进行模拟,在游戏结算的时候,估计1秒内就能结算完毕拿到战斗结果进行战斗结算。
如果不是一起模拟,那么服务端为了校验结算,就要一次性从头跑完整局游戏从而拿到战斗结果。
按照客户端的性能数据分析的话,服务器要运算大约10秒才能拿到战斗结果,相比之下,玩家需要等10秒左右才能看到战斗结算。
实际体验的时候,结束动画播放完毕到显示结算结果不到1秒。不过也许服务器在客户端开始播放游戏输赢动画时就开始一次性跑完整局游戏,从而把10秒掩盖掉一部分也不是不可能。
当然,服务器性能我没实际测试过,也许一场20分钟的游戏,服务器1秒内就计算完毕也是有可能的。
点2.因为服务端是在和客户端同步运算,相比服务端不跑游戏逻辑会对外挂会多了一些检测手段。
比如对于修改游戏逻辑数据的外挂,可以每5秒对关键逻辑的内存数据进行一次信息摘要计算,拿给服务器进行对比。
信息摘要还可以拿来检测帧同步运算客户端们和服务端的一致性问题,尽早发现不同步。
不过这个作用不是很大,原因是帧同步天然对大部分外挂天然防御,因为客户端只会上传操作给服务器,服务器不会接受血量,位置等客户端数据,其他客户端也不会接受血量,位置等客户端数据。
基于帧同步开发的游戏比较难防御的外挂主要还是透视。
点3.既然服务端支持生成快照,那么客户端也可以支持,快照除了在恢复上有用,还对帧同步运算的一致性问题排查很有帮助。
有了信息摘要对比就可以提前发现不同步问题,有了快照,就能够对比客户端之间和服务端之间的快照差异,帮助找出出问题的点。
其实这个想法有点像调试的断点功能了。信息摘要的生成可以不仅仅在一帧末,在内网开发版本还可以在每处函数入口都生成信息摘要进行对比(虽然这个过程执行效率有点慢,但是可以接入自动化处理,节省人工),及早发现帧同步在哪一个函数调用后发生了不一致,并且在快照的帮助下能够对比出不一致的数据在哪里。
分析3,服务端是在和客户端同步运算的猜测,有一个有利的证据能够有力的支持该猜测,就是玩家掉线后,或者长时间未操作,该玩家会被AI托管,进行自动游戏。
当然现象也不是百分百支持该猜测,因为玩家被AI托管至少有以下几个方案:
方案1.服务端是在和客户端同步运算,因此服务端有了完整游戏逻辑,就可以进行AI处理,AI输出的是该玩家的额操作即可。
方案2.服务端没有在和客户端同步运算,AI托管是通过委托给某个正常的客户端来运算,被委托的客户端把AI输出的结果上报给服务端,实现AI托管目的。
这个推论有一个小实验能够否掉,就是全部玩家都掉线了,如果玩家都在被AI托管,那么这个推论就被否了。
当然这个需要找朋友来一起试一试(我自己没那么多号)。
最高画质,60帧,玩了20分钟游戏,手机温度并不是很高,LOLM研发团队的优化做的还行。
———————————————————————————–
双通道网络通信。游戏内说明如下图2

图2
分析: 双通道指的是WiFi和移动网络2条网络通道与服务端连接,对于双通道通信,有以下2个猜测:
猜测1,游戏过程中,同一条服务端消息会无脑在2个通道上面发给客户端。这个办法其实实现起来是很简单的,只需要对消息进行编号,网络底层维护2个socket收发,处理一下”双网卡”逻辑正确性,客户端收到重复编号的网络包业务层丢掉即可。
但是存在的代价就是服务器出口双倍流量,客户端在网络处理上双倍CPU开销,客户端连接了免费WiFI,还要移动网络流量开销(部分玩家可能有抵触心理,也许这正是LOLM会主动提示玩家,让玩家去选择的原因)。
这种方法虽然看似简单,但是除了上述代价之外,可以说是在网络传输过程是最优解了,理论上同一个编号的网络包最先到达客户端的通道,就是该包质量最好的通道(这有点每个网络包都在动态选择最好的通道的意味,通过局部最优解达到全局最优解),这个符合图3的描述。
猜测2, 针对猜测1的 代价(服务端出口流量大等),有以下优化推测:
推测1.对于状态同步的游戏,如果服务端下发的位置数据发生了卡顿,相比其他数据,玩家会更容易感受出来卡顿。
因此可有考虑位置数据才走双通道,玩家自己客户端主动技能释放的收发包也要走双通道,因为玩家自己的操作体验对于主观感受最明显的。
对于消息提示,扣血等信息,其他玩家的技能数据等不走双通道。
推测2.帧同步的话可选择的就少了,网络同步消息类型可以分为帧消息和非帧消息,帧消息是很关键,影响全局不能省略。
非帧消息有,玩家聊天,地图标记等不影响帧同步结果的数据,可以走单通道,达到节省流量的目的。
当然,也不是每个网络消息包都走双通道,某个通道可以只走单号帧数(如第1、3、5、7帧)来减少一下流量开销。
猜测2的思想就是在权衡流量和玩家感受收益中找到一个平衡点。
猜测3,这个思想是从协议层入手,在重传机制上做文章。
网络正常情况下只正常通信不走,当认为需要重传的时候,走双通道通信,尽最大努力重传,当网络恢复正常就不走双通道直走一个通道。
这个过程还可以有主通道的概念,主通道是主要走的通道,是两个通道中质量最好的,并且主通道要实时根据实际网络质量在双通道中选择一个作为主通道。
该猜测的实现难度最大。比如kcp内部一个通信标志只会和一个socket绑定,这里要实现多个socket绑定,改造起来难度不小。
注1:单通道指的只是使用WIFI或者 移动网络。
注2:其实上述几个猜测并不是完全三选一,他们部分思想可以相互融合。
综上,对于猜测们的总结如下表1:

表1
为了验证上述猜测,进行了一下小实验,开启和关闭双通道网络通信功能流量对比。
单个玩家帧操作流量开销猜测:
1.移动摇杆方向数据2字节,值区间[0.00-360.00]。
2.客户端16个技能按钮按下与否状态,占用2字节。
3.技能按钮瞄准方向键2字节,考虑到玩家操作2个按钮方向,就4字节吧。
4.网络包头当作16字节处理。
假设帧同步的网络同步频率是20帧/秒。 一局战斗10个玩家,单个客户端上传流量最大值是0.48kb/s,单个客户端下载流量最大值是4.8kb/s。
10分钟战斗,单个客户端下载流量大约是2.8mb。
如果压缩一下流量消耗可能会更少。
有了大概猜测,接下来 进行实际测试:
WiFi: 18分钟游戏,流量消耗 5mb。
移动网络:18分钟游戏,流量消耗4.2mb。 18分钟时把客户端进程杀掉,重新进入战斗,此时流量额外又消耗了约4.4mb。经过多次测试,游戏时间越长,额外的消耗流量越多。并且额外消耗的流量数值也正常游戏消耗的数值差不多,这个测试结果几乎能够证明杀掉进程后的游戏恢复客户端是在从第一帧开始运算到最新的帧,而不是根据快照来恢复。
双通道: 15分钟游戏,WiFi流量消耗 4mb,移动网络流量消耗 2.8mb。
结合实际流量测试结果,LOLM的双通道方案更接近猜测2和3。
———————————————————————————–
客户端登录请求频率限制。现象:如下图3

图3
分析:
一般来说服务器处理登录请求的能力相比其它请求类型的开销要大很多。 服务器处理登录过程中可能存在开销大的可能的点如下
点1.从数据库加载玩家数据到游戏进程,redis和MongoDB还要好一些,后端存储引擎是MySQL的话,会更慢一点。
点2.平台SDK账号登录验证,一般这个过程是用http协议处理,曾经做过如下实验:
如果是单线程异步IO处理http相关请求,大概每秒只能处理2000条请求。
如果是单线程异步IO处理和redis通信,每秒能够达到2W条请求。
如果是单线程异步IO处理和客户端的公网通信,每秒能够达到1.5W条请求。
点3.游戏登录成功后,客户端可能会发起大量数据请求(比如游戏活动数据,商城打折信息,好友列表),服务器也可能会主动推送大量数据(如邮件),导致登录成功后接下来几秒内数据流量增加很多。
这些流量的背后都是在消耗服务器CPU。
对于服务器宕机这种突发情况,登录排队,登录限制时间间隔,都是可选择的方案。
在架构设计上如果把这些不同类型的请求进行微服务化,在应对突发/精确节省服务器成本情况方案上选择会更多,比如服务降级,弹性伸缩等。
综上,一般来说服务器处理登录请求的能力相比其它请求类型的开销要大很多,突发情况会造成服务器平均响应时间变慢,卡顿等问题。
LOLM做登录时间限制是不错的一个预防方案(也许还有其他方案,我没体验到,比如排队)。
———————————————————————————–
弱网表现。原本想在PC上面模拟器玩,抓包,模拟弱网来分析分析。但是发现LOLM不能在Android模拟器玩,后面有空了再尝试在PC搭建一个代理服务器,让手机连接PC的代理服务器,再连接到游戏服务器。
这样就可以在PC上面抓包和模拟弱网了。