9月23日,首届“梦想·匠心”腾讯游戏开发者大会于深圳举行,在技术分论坛上,腾讯互动娱乐《天涯明月刀》项目技术总监张正分享了《天涯明月刀》的后台技术创新。拥有15年游戏从业经验的张正,于08年加入腾讯,先后参与或主导《轩辕传奇》、《天涯明月刀》等大型MMORPG项目的后台开发。张正于本次论坛上讲述《天涯明月刀》服务器开发中的技术深度探索,在业务复杂度控制、性能及承载、系统高可用等方面的创新性技术方案。
以下内容为演讲实录
接下来由我给大家带来分享,我分享的题目是《天涯明月刀后台开发的技术创新》。我是腾讯互娱北极光工作室群后台技术中心总监张正,我加入腾讯接近10年,从2004年开始做游戏后台开发。从2011年加入到《天涯明月刀》,做到目前为止,做了6年半左右的时间。
《天涯明月刀》的用户还是很多的,但现在市场逐渐往手游倾斜,所以很多人可能还是不了解市面上端游的产品。《天涯明月刀》可以说是近两年腾讯公司在端游上的标杆,这里主要介绍技术方面的特性。《天涯明月刀》是非常大型而复杂的端游项目,我们在服务器端手写代码的规模就已经到150万行,其中像我们最核心的场景进程,单进程手写代码超过50万行,复杂模块代码量到10万量级,像技能、移动相关的。天刀服务器单组同时在线可以到4万,这是线上实际达到过的,而不是停留在设计容量中的。关于性能方面,单核的承载可以到千人左右,这时技能的释放非常频繁,而且不断有强交互、位移这些出现。另外,说一个比较特色的技术点,天刀的服务器地形是真正的3D,在大型MMORPG里面是一个挑战,后面我会详细展开讲。我们7月份推出了航海资料片,里面的海是非常大的,64公里见方的范围。
《天涯明月刀》后台技术的创新路径
来看创新的定义,有别于常规的思路,为了满足需求,找到新的路径和新的方法。天刀在服务器技术方案上怎么样找到新的路径?主要通过三个方面:一是通过技术提升玩法设计。二是游戏体验方面做极致的优化。三是运行质量、稳定性方面追求更高。
《天涯明月刀》后台真3D技术
先来做一些类比,比如有一款3DFPS的产品:FPS里面有爆头的设定,打到身体包装盒覆盖的地方需要有特定的伤害。FPS游戏的房间里面的人数是比较少的,大概20来人左右,另外地图比较小,应该是在几百米的范围内。它用的技术方案是在服务器端运行UE3。另外一款是3D骑砍项目,里面会有140人的对战。技术方案和前面略有不同,是用Havork引擎在server上面跑,也是以开房间的模式。
天刀在服务器做3D化时会存在哪些挑战?天刀的特点和早年间的MMORPG不一样,那些游戏的技能释放频率比较低,更多是站桩打。天刀作为一款ARPG游戏,打起来的节奏非常快,攻击方和受击方都要边打边移动。另外我们的地图很大,产品设计之初就确定为产品的特点或者优势,边长4公里,单张地图的大小达到16平方公里。从MMOPRG玩家的角度,他们对精度的感知更低,所以我们无需采用前两者那么高精度的方案. 为了让玩家在游戏内自由迁徙的同时,服务器各个场景的负载还是能够做到比较好的均衡,我们把每个场景都加载了全部的地图资源。天刀的单张地图有16平方公里这么大,精度还需要到单个碰撞格子0.5米,所以单张地图的数据量非常大,五六百MB一张地图,全部加载之后达到7GB。
早期的时候,天刀服务器是传统2D方案,首先我们想到的,是把平面变成多层,2D本来是XY的网格结构,用很多层的网格结构,似乎可以构建3D的世界。但是后来发现这样不行,它会有很多复杂情况处理不了,比如一楼和二楼之间架楼梯,楼梯算几层?而且有些楼梯在塔里面,是盘旋楼梯,那就更难处理。另外策划需要刷怪或者刷物品,在编辑器里面需要处理刷上去的每个怪或者每个物品放在几层, 这就变得非常复杂。所以后来我们想到一种方案,我们受到voxel体系的启发,在编辑器里用voxel把世界描述出来,经过一些处理最终得到右边的基于voxel的3D场景描述。简单说,垂直投影到一个平面上还是XY,但是在Z轴的方向,垂直的方向,相当于有高度、有层的概念。
场景碰撞校验算法
从剖面图上可以看到最低处绿地都是0层,我们从某个0层格子往上看,投影上有一个实体,我们认为这里1层,再往上是2层;如果中间没有这个实体,就是从0层到1层。
这样的话,上层业务逻辑来调用时,先做行走路径的碰撞,当然首先检查有没有静态阻挡,接下来做高差的判定,可以直接走上去的高差阈值应该是在1.5米左右,人的身高在1.8米。接下来做碰撞判定,如果空间非常矮也走不过去。对于飞行是单独走飞行判定,需要判定往前飞会不会被挡住,以及飞过去的空间是不是可以容纳。
回到前面说的问题,单张地图这样描述,会导致数据量很大,大到600~700MB,我们针对这里做了很多的优化。首先是空间的优化,我们单独做了一个内存的共享。它的额外好处是可以提升服务器的加载速度。因为单个物理机有很多核,产品的进程都是单线程的架构,我们需要多个场景分别启动,每个进程都去读几G的地图数据,这样启动非常慢。如果先有一个进程把地图加载到shm,其他的进程都直接attch上去,这样速度就会快很多。我们还做了时间的优化,单个Grid上可行走的方向是4个,两个相邻格子之间的联通关系都是静态确定的,不需要运行的时候一遍一遍判定,所以我们做了联通性的cache,在运行时cache的命中率可以达到95%。
最终做了这些工作我们可以得到很好的性能,大家可以看到,移动每秒内的耗时在140毫秒,即便把移动加上技能AI都调用位移相关的逻辑,加在一起,我们行走层相关的计算也会在20%的CPU消耗以内,应该说是非常可以接受的数据。
《沧海云帆》资料片超大海洋实现
以上说的基于voxel的方案是2014年以前做到的。到2017年的时候,策划表示我们加一个航海的玩法吧,接下来我介绍一下航海玩法会带来什么样的挑战。
简单回顾前面voxel地形,大小是0.5米,我们用的视野管理是非常经典的九宫格管理方式,我们把每个格子叫一个Around,边长54米。
航海需求下出现这样的问题:首先是航海的地图,我们要求它要非常大,因为有船的航行速度摆在那儿,不能说开一小会儿就到地图边缘不能走了,这样体验就非常假。最终我们定了一个大小64公里的航海地图,是原来地图的256倍。对于对象的碰撞检测,原来无论人还是怪,我们把它设为一个点,不在这个格子上、就在下一个格子上,只用检测一个voxel或者相邻的4个voxel就可以。但船只非常大,50米乘以20米,也就是100×40个格子,总共4000个格子那么大。这样就会带来非常大的问题,如果大家有在MMORPG的格子地形上做过多格对象,就知道这样的设计非常复杂,而且开销非常高。正常的视野,九宫格的边长相当于54×3,也就是162米。但开船在海面上,视野很大,这个视野会到3000米那么远;陆地与海面并存,你开船时会看到前面的岛,然后可以下船来到岛上去,像真实的在海上一样。当然你在岛上办完事还可以继续开船。内部讨论时,我们一开始问,能不能进到一个岛,就刷一个副本,画面一闪就进副本,副本刷完再从岛里出来。但这个体验不是足够好。我们也想过更为简单的方案,在服务器侧,因为船那么大,相当于一个房间,玩家在房间里搞来搞去就可以,客户端上看到的是船在动,但是服务器上所理解的是海在动,而房间是固定的。
最终,我们还是想挑战一下更好的技术方案。比如在保持voxel的精度不变的情况下扩展地图。但这样直接带来的问题是单张地图的内存就会成倍往上增,会到50G的规模。第二个方案:是不是可以把精度搞得粗一些,但对于海岛来讲,阻挡就不能做得非常精细,岛上的视野管理也会出问题,单个around会变得非常大,广播就会成倍往上增。第三个容易想到的是无缝地图,无缝地图甚至可以被当做包治百病的方案。但我们已经走在了voxel的路上,每个场景单独隔离,每个地图对应一些副本,切地图的时候,它就会切进程或者副本对象。如果往无缝地图改,风险是很高的。另外和上层之间的交互变得和以前完全不一样,以前一个地图肯定在单个进程里面,现在一个地图分成很多块,每块在不同的进程里面,从一小块走到另外一小块就要迁移到不同的进程去,这里会涉及到一些事务的处理,特别是船走着走着,船头的人在进程A,船尾的人在进程B,会非常复杂。
最终我们找到一个方案,说是折中方案也好,复合方案也好,就是双精度地形。大地图用粗精度描述: 海其实和地面还是不一样的,地面上总是会有盖房子、立柱子,人穿不过去。海面除了岛以外,大部分的地方都是可以通行无阻的,所以地形细分粒度不需要很小。海岛的地形和普通地图一样,还是基于0.5米见方的格子组成的,我们把它作为插件融合到大的航海地图里面去。
我们做了分层的视野管理,航海地图用了边长很大的around,在岛边缘的时候做切换。这个方案的优点是可以满足几乎所有的要求,比如对视野的要求,对地形精度的要求,包括内存占用。刚才说海的地图,本身并没有多少需要描述地形的数据,改动局限在地图碰撞和视野管理的底层,对于上层的逻辑就可以直接像原来一样该怎么用就怎么用。视野的范围,大地图发生的行为要广播到周围的around,周围如果有海岛,会把消息推给海岛。如果海岛和海面的交界发生事情,这里会有一个底层边缘升格的策略,把边缘的事件广播到周围的海图上去。
看一下这个混合方案在技术维度上的结果。在空间复杂度上,虽然一张海图还是被整为一个文件,文件大小只是增加到了4倍,地图却是原来的256倍那么大。时间复杂度上,其实并没有非常明显的增加,在视野管理上同时满足了海岛上的视野管理和海上的视野管理,并且两者之间是可以有非常好的无缝切换。
这是天刀上线前后的一个slogan,你想要的必将实现。无论是策划团队还是技术团队,我们都希望给玩家打造最好的游戏体验。
《天涯明月刀》千人群战优化
这是天刀640人盟会争锋战,在同屏里看到的玩家很多。第二张图是800人的修罗城,在刚上线的时候,由于和策划团队之间缺少提前的沟通,当时面临一个非常被动的情况——这个玩法在某些服灰度上线了, CPU一直在100%,延迟达到10秒,相当于完全卡住不动,我们称为雪崩。
优化面临着非常大的困难,我们的代码复杂性比较高,前面提过一些,比如代码量非常大,单个核心模块非常复杂,它是动作性的游戏,而且技能由很多子技能复合在一起,天刀的技能系统策划不只配表格,还可以在里面嵌脚本。我们已经在运行时把配进去的脚本转化为C++的数据结构执行,但是即便如此,单技能的消耗还是非常高的,有2.5M CPU周期。业务复杂性上,也不能说玩法设计太变态,但是最终达到的情况就是这样,玩家的聚集非常密集,150米见方聚集了800个人,因为天刀开始希望打小怪爽快一点,给每个职业都设计了AOE的技能,又因为是武侠动作游戏,所以公共CD很小,技能释放很频繁,每个人都使劲放技能,每个技能都可以打到周边一片人。参与人数每增加25%,负载增加56%,这是经过实测和测算的,对应我刚才举的例子。
天刀里面有哪些主要的模块在提供复杂度和性能消耗?主要是移动、战斗、数据同步和AI。在战斗方面我们分析下来,最主要的几个消耗:目标扫描,因为AOE技能多,目标扫描里面又需要判断目标和自己之间各种各样的关系,像这种大型MMORPG游戏里面,很多模块会产生敌我关系判断的影响。连通性检查,前面提到基于3D的地形判断上面,比如远程技能能不能打到目标,中间隔了许多格子要去计算。静态修饰是指,每个技能在释放过程中可以被很多的buff影响,如果有某个天赋,可能技能过程就变了。另外动态上了buff,或装备特殊装备和特殊武器,技能就可以发生变化。
统计下来所有业务开销,技能在里面占了接近60%,但是技能系统相关代码的行数是10万量级的,技能的数量也非常多,光主角技能就是几百的量级,我们逐个解决是不太可能做到的,更无法短时间内做到。再看技能吞吐量,那时候能支持的吞吐量大概是在400人、每秒500个技能,在800人的时候,期望每秒释放的技能数要上千。实际还没到一千系统就过载了,所以首先要做的事情是把过载解决掉。我们建立了防过载的体系,把CPU去做压力分级。有内置探测CPU占用率的功能,如果CPU达到了50%以上,就开始触发降级的策略,让逻辑模块的服务内容做一些降级,比如AI的思考频率低一点,允许释放技能的频率低一点。到90%的时候就要触发过载保护,过载保护后面展开讲。真正系统压力高企的时候,很多模块的策略就会发挥作用。
我们为技能做了这样的策略:技能有配额池,每释放1个技能就从配额池里面提取1个配额,每过1秒会重置这个配额。重置有一点问题,是到底重置多少。前1秒如果有1000个配额,把CPU跑满了,下1秒我们会让配额快速下降,直接减半。这样CPU就可以快速降下来,之后我们就逐渐再把配额升上去, CPU占用率越高,我们给它往上升的比例越低。一开始从过载的状态迅速把配额降下来时,玩家体验会变得非常差,差的表现是连按12345滚键盘但技能放不出来,逐渐把技能配额升上去以后,玩家体验就会逐渐恢复。
其他模块降级的策略会有AI思考间隔,移动请求降频,技能扫描的范围,有些AOE技能打得很远,在系统非常繁忙的时候就不让它打到那么远,只搜索附近的玩家。做了过载保护以后,有这么几个成果,系统已经不是一直维持在过载状态,像聊天、移动就可以变得流畅了,但是技能的延迟还是非常高。CPU曲线会出现狗牙状,非常不稳定,还在不断的往顶上碰,上去以后就会被砸下来,之后又会由玩家行为顶上去。
前面通过过载保护让系统能够喘口气,之后我们想怎么优化。
-
实现层优化的方法比较常见,像空间换时间、变化比较少的结果放缓存,降低重复计算。另外可以用性能分析工具找哪些函数消耗最高,或者哪些消耗偏高的函数看上去不合理。
-
业务层分两个方面,这是腾讯内部经常提到的海量服务策略,像柔性可用,我们设计几个级别的服务,像刚才讲的反馈模型一样。另外可以做一些有损服务,比较经典的说法是玩家渴了,但是他说想喝果汁,我没有果汁时给他水喝,也可以很大程度上解决问题。
-
架构层,我们也可以做一些工作,按照功能纵向切分,把旁路功能切分到独立的进程里去,还有, 业务无关的网络IO包括打解包都放到独立的线程里面做。另外也可以在架构层做横向切分,比如把负载调整得更好。
《天涯明月刀》业务层优化
我们还是从业务的角度对群战问题做分析。在释放频率上,大概0.8秒就可以放1个技能。小范围里有这么多人的情况下,玩家虽然很在意我是不是卡,但是不在乎技能应该打到的精确目标。比如周围有ABCDE,而C没有打到,他不太能感受到。战斗策略方面,那么多人聚在一起互相砍的时候,他们更想看到的结果是我放技能,旁边的人死了。到底伤血次数有多少、每次的伤害值,他并不关心。而单独打斗就对于技能的表现,对释放频率,对一边打一边跑这样的东西很敏感。然后看技能喜好,我们给每个职业都设计了比较好用的AOE技能,经过统计发现,其中有一些消耗占比很高。它们的共同点是攻击范围都是AOE,类型均为高频EOT(随着时间生效的技能),比如太白的无痕,3秒生效30次,还有归玄频率为2秒10次,这都是非常频繁的。我们得出的思路是利用这样的业务特点,利用玩家的体验倾向,对重点技能做针对性的优化。
最终可以得到技能优化专用版,对技能原来每秒10次、每次20点伤害,可以压缩成每秒1次,每次伤害200,也就是说降低频率、提高单次效果。另外策划可以自己做一些简化,原来有些技能的跳转是非常复杂的,简化后逻辑效果仍然和原来持平。
有了这样的专用优化版技能,感觉可以松口气,但还有额外的问题。比如,我们的地图很大,地图这边可能会有一千号人凑在一起猛打,但是另外一边就会有人插旗单挑,或在单对单PK,这种情况下玩家是非常在意技能效果到底是不是那么真实的,因为他这时候只有一个目标。另外,单个场景进程里面会有多张地图实例,如果群战地图用技能的优化版本导致副本地图里也受到影响,这是不能被接受的。 我们需要准确的识别群体行为。
来看怎么试图识别地图上的热点区域。基于around里的人数和技能释放频繁度,我们可以把around标记成热点区域,在区域里用专业的技能优化版本。还有热点漂移问题,在群战里面玩家一边打一边跑,热点就会往下面的around移过去。形成热点需要若干秒的统计累积,这种情况下就会出现毛刺,尤其战场在格子边缘,不断来回拉扯的时候就会更容易出现毛刺。我们用热点延迟降级方法解决了热点漂移的问题。这样玩家在且战且走的时候,它的技能的计算性能是平滑的。这个优化以后技能的开销呈量级往下降,而且技能的切换是平滑的,玩家几乎无感知。
做了业务层的优化之后,大家可以看到CPU曲线已经是比较平稳的状态, 800人混战CPU占用50%左右,支持千人规模的群战是没有问题的。
《天涯明月刀》后台逻辑层可用性提升
最后说一下后台逻辑层可用性提升,我们怎么样在常规的可用性保证上又做了更多加深的工作。 腾讯自研的项目,凡是有状态的进程,要把很多的上下文数据分配到shm上,作用是, 一旦发生crash,我们可以重新拉起进程执行体,把shm数据重新加载上去,然后基于原有状态往下跑。这样数据并没有丢掉,听上去很美好。
但是这样的方案还是存在一些问题,相信很多项目都有遇到过。因为真正导致进程宕掉的故障点可能还在,可能再次触发。resume要求拉起来很快速,具体体验上,玩家可能感觉卡了几秒,然后又可以继续玩。但天刀的进程里状态数据很多、使用内存很大,人工判断一下故障点再让进程拉起来是不可能的。
前面提到了故障点,接下来我们对故障点做一下分析。 MMORPG进程的驱动力之一,数据包,无论玩家请求还是内部进程请求或回复,都可以认为是数据包。驱动力之二, 定时器,时间到了就执行定时器里面要求的内容。驱动力发生错误还好处理,在重新起来的时候可以把驱动力扔掉。比如resume后会略过之前的timer,跑下一个timer。再看驱动力之外的因素,所有的程序其实都是逻辑加数据,如果维护的内存数据出现问题,进程也会产生异常。我们很自然的想到做自动检测和隔离,隔离一些数据,也可以隔离一些逻辑。因为逻辑如果频繁造成resume,玩家也不能玩。比如他进行一个操作发现卡了,就又再试,一直试,然后这个进程就不能用了,这样的情况我们也希望隔离。
但是隔离的时候算法不能太复杂,要保证速度,又不要出现难以追查的错误。
我们建立了故障点自动检测和隔离的系统,核心是有两个过滤器,驱动源的过滤器和数据源的过滤器,所有的驱动力来了,要检查能不能从过滤器过去,过去以后才能跑;对象也需要每次访问的时候看在不在过滤器里面。(图上)这里是非常小的数据结构。我们把运行时用到的东西放在记录列表里面,resume的时候触发分析。尽量做最小粒度的隔离,包括数量的粒度和对象层级的粒度。其实这个系统刚上线的时候出现过故障,我们把一个帮派对象隔离掉了,结果和这个帮派里所有玩家都认为自己没有帮派了,而且把这个信息存盘了,于是造成更大的故障。到后来我们设置了一些白名单,有些类型的对象是可以被放到隔离区的,有些东西如果真的非常大、非常复杂,和其他地方耦合度很高,我们还是倾向于其他处理方法。下面是最后实施的效果(见PPT)。在版本更新、解决整个故障之后,再手动清空隔离内容。
回到我分享的主题,我们的创新路径是这三个点,具体展开,就是3D地形和航海、千人群战优化,以及resume的加强版。
我有时候会想,如果站在2011年看现在天刀后台做到的东西,真的会觉得还是挺厉害、挺精巧、挺新颖的。同样,在2011年的时候去看现在的一些电子产品、电器产品,我们也会有这样的感觉,感觉真的是一个完全新的东西,但这是靠不断的探索、不断的积累,最终才达到了现在的高度。
最后跟大家分享苹果的设计总裁Jonathan Ive说的话,想做得不同很容易,但是想做得更好非常难。希望我们天刀能越做越好,给玩家提供更好的体验,也能给大家今后带来更多更好的分享。
我的分享到此结束。谢谢大家!