关于MMORPG多人对战中热点问题的解决思路讨论

一. 引子

  本人是重度游戏爱好者,MMORPG当然也体验过很多,从早期的传奇、奇迹到冒险岛,从天下三、剑网三到天刀、逆水寒,其中有一个共同的问题:人一多就卡。拿剑网三举例,大攻防小攻防简直不要太卡,尤其是一波对冲,如果你不屏蔽人物、不降低画质,那简直就是在作死的边缘试探。为此,本文试分析如何解决MMORPG中多人对战造成的卡顿问题。

  卡顿分为个人电脑卡顿和服务器卡顿两种。其中个人卡顿可能是由于显卡性能不够(引擎优化不够)、内存不足(客户端优化不够)、网络延迟高(大多数是因为网太垃圾了,也有可能是客户端服务端优化不足)等原因导致。这些不在本文的讨论范围内,因为每一个展开都会是极多极难的问题,本文仅专注于讨论大攻防时由于人多导致的服务器卡顿问题,即如何做到一个玩家在进行大规模对战时游戏体验和JJC以及战场体验接近或者相同的问题。

二. 热点问题的由来和解决的痛点

  游戏发展到现在,对服务器来说瓶颈其实并不在于网速,而是在于当几百人对战时,一般会集中在一个很小的区域范围内攻击BOSS或者双方互相攻击。而这种情况下,每个人的技能、移动都需要计算,每个人受到的技能、别人的位置信息也需要实时更新,这些数据计算难以拆分、CPU也难以快速计算完成,因此造成了一个计算带来的延迟。这个延迟导致了逻辑帧帧率的下降,并因此导致了渲染帧的减少。为了追帧,就不得不做一些处理,因此在玩家严重就表现为大家突然静止,然后突然瞬移,或者产生快速移动现象。

  由上,不难看出主要的问题在于单位时间内的计算量可谓是不低。以剑网三为例,在一次大攻防里,400对400人的战斗,还要考虑到特殊门派如五毒的宝宝、长歌的影子,可以假设是1000对1000,即每秒需要完成百万数量的技能计算。

  有人会说,多开几个服务器不行吗?多几个电脑(CPU)一起算不就好了吗?这里不得不说,更糟糕的是,玩家释放技能会造成玩家之间有着强依赖关系,拆分起来有很多问题需要讨论。如A发出向B的攻击,B发出了向C的攻击。如果A的伤害足够击杀了B,则B对C的伤害如果在击杀之后则应该无效。如果将A、B、C拆分在不同的服务器上计算,那如何保证如例子中一般取消B对C的攻击伤害呢?如果通过服务器间通信来保证消息的一致性,通信的延时损耗和完成逻辑判断需要的开销会不会比拆分得到的收益更多呢?

  因此,我们不可以将每个玩家都拆分出来单独计算,因为需要考虑玩家之前的强关联性,同时还要考虑拆分后为保证数据一致性的通信时延损耗。

  对此,我觉得可以从以下两个角度着手考虑如何解决该问题。

  • 从分级合理拆分的角度考虑解决该问题
  • 从优化流程的角度考虑解决该问题

三. 分级合理拆分

  • 逻辑时间管理
      拆分一大关键问题是同步问题,因此这里可以采用一个类似于lamport clock的自增逻辑时间来标记每秒内收到的所有玩家操作顺序,并以此顺序来作为后续拆分后一些先后顺序的判断:比如玩家释放的技能和玩家死亡时间的先后顺序。

  • 拆分技能
      在MMORPG游戏里技能一般分为两种:AOE技能和单体技能。为了减小计算量,我们可以将AOE技能单独拆出来而不作为玩家间关联的依据,而是判断玩家所处的坐标内是否有该技能效果。这里需要考虑伤害发起玩家如果死亡则伤害停止。

  • 热点中的热点单独计算
      在大型对人对战中,看似玩家间的攻击杂乱无章法,但是实际上大多数时候是有序的,如
    (1)大攻防中的大小经营BOSS
    (2)小攻防中的箭塔、战车、大旗
    (3)被指挥标记的集火单位
      这些单位存在一个共同特点:被大量单位集火,因此可以作为热点中的热点,单独进行计算。我们将该热点收到的技能伤害以逻辑时间为顺序列出,以哈希表 + 链表或者跳表的形式存储(空间换时间)。等待其他玩家是否有死亡信息发生在其释放技能之前的事件通知,若有则将其从该表中剔除,最终生成计算结果。

  • 邻接表拆分关联玩家
      这里可以模拟Trie树的思想,从一个dummy根开始,以邻接链表的方式将玩家进行分类,然后对dummy根的儿子们进行拆分计算。这些儿子的计算结果可以直接发送给对应玩家,同时发送一份死亡通知给3中的热点用以计算。

四. 计算流程的优化

  • 优化技能和判断逻辑
      这里的优化技能逻辑其实是针对开发流程中的策划来说的。在大型MMORPG团队中,一般技能等的释放、判断逻辑会交由策划通过编写脚本来完成,如python或者lua等等。这里其实可能存在两个问题
    (1)策划一般不会精通算法,写出来的逻辑可能复杂度很高,每一处的高复杂度叠加后就会产生极大的不必要延迟
    (2)脚本和C/C++的调用会产生时间成本
      这里可能的优化方法有
    (1)类似于 C++的stl,写一个基础库供以策划调用,避免了策划写出垃圾代码和大量低级BUG的问题
    (2)做一个脚本编译转成C/C++的中间工具,解决问题(2)

  • 服务器流程优化
      最近在学习的时候了解到了一个近几年比较有新意的东西:DPDK(Data Plane Development Kit)。详细说明可见其官方说明,另外这里有篇中文文献写的也不错。
      处理数据包的传统方式是CPU中断方式,即网卡驱动接收到数据包后通过中断通知CPU处理,然后由CPU拷贝数据并交给协议栈。在数据量大时,这种方式会产生大量CPU中断,导致CPU无法运行其他程序。而DPDK则采用轮询方式实现数据包处理过程:DPDK重载了网卡驱动,该驱动在收到数据包后不中断通知CPU,而是将数据包通过零拷贝技术存入内存,这时应用层程序就可以通过DPDK提供的接口,直接从内存读取数据包。
      这种处理方式节省了CPU中断时间、内存拷贝时间,并向应用层提供了简单易行且高效的数据包处理方式,使得网络应用的开发更加方便。但同时,由于需要重载网卡驱动,因此该开发包目前只能用在部分采用Intel网络处理芯片的网卡中。
      对于玩家来说,总体感知的时间是发包+处理数据+收包的过程。因此对任意部分如果可以进行一些比较显著的优化都会对整体效果起到比较好的表现。DPDK在收发包、数据处理方面都有着较多的优化,该部分如果有比较成熟的生态系统我相信对提高服务器表现绝对大有裨益。

五. 总结

  由于本人缺乏很多方面的实际开发经验,本文仅从一个游戏玩家的角度进行分析,一定有很多有失偏颇或者过于天真的妄想之处,也可能因为自身知识能力的关系视野狭隘没有看清各大游戏厂商的设计思维。所以还请多多包涵指点,聊以娱乐。

坚持原创,坚持分享,谢谢鼓励和支持