150ms流畅体验 NBA2KOnline如何网络同步优化

发表于2015-04-30
评论12 1.66w浏览

前言

为了不想彻底沦落技术理论文章, 所以对于技术细节,仅贴图展示一下。希望大家阅读能找到一些感兴趣的内容一起交流。

NBA2KOnline介绍

NBA2KOnline游戏是我们Take-Tow,2K sports以及中国团队VCC合作开发的一款拟真篮球游戏。

 

目前,游戏采用服务器帧同步技术 该技术助长游戏不断攀升到更高的目标

目前,在我们的体验统计中,超过85%的玩家操作延迟 < 150ms

操作延迟为玩家输入,看到输入表现时间差,150ms流畅的分界线

 

 

 

NBA2K原本是一款Console game从2K9开始登陆PC平台游戏真实呈现NBA比赛,富有技巧的操作极其逼真的物理碰撞表现,是其最大的特色

项目团队是分布开发模式VCC负责Game core,我们负责Lobby,期望能发挥各自的优势。

 

 

 

 

LOL不卡,为什么NBA觉得

玩过LOL,DOTA的人,再玩过NBA2KOnline你会情不自禁的觉得NBA更卡,我们也想了很久,其实这里面是有原因试图分析下

定义一个名词:持续即时操作

所谓持续性操作, 典型就是要按住键盘或移动鼠标,进行移动或选择目标

画面卡顿造成视觉上停顿更重要的是打断了【持续即时操作,画面停顿可以脑补, 但操作停顿就不能愉快的玩耍了。

分析下各个游戏的持续性操作:

 

 

持续即时操作

卡顿影响

CF

移动,射击方向持续即时操作需要精确控制时机

LOL

移动点一下可以自动寻路(相当于操作计划,技能也有选择范围有一定缓冲。可以有一定反应时间。

NBA

走位,传球,上篮,抢断盖帽等全部持续即时操作需要精确控制时间。

 

可看到游戏类型不同网络要求差别也大。越是即时和拟真的游戏,操作延迟和画面稳定要求越高NBA这种体育动作游戏,是所有游戏要求几乎最高,做个比喻,LOL玩家像是拿炮CF是拿的,NBA靠肉搏,对操作敏捷要求高。

             

另外一种碰撞细碎来说   NBA  CF > LOL, 碰撞点太小太细就意味着对操作延迟,和画面流畅性要求更高。

架构P2P + Lockstep问题

最初网络同步模型采用P2P + Lockstep方式,这也是欧美公司一脉相承方法这部分逻辑集成在Game core

特点是:

n         比赛时客户端之间选择一个做主机。

n         P2P:该P2P是广义的概念,包含局域网直连以及我们补充的P2P穿透P2P Server转发等技术。

 

这种方式,局域网自然没问题,可一旦到了复杂的中国Internet 就问题不断

n         一人卡,大家卡同步机制缺少容错,一个客户端按住窗口边框主机就处于等待状态,结果其他客户端也都处于停滞类似的 只要一个客户端卡顿会造成所有人卡顿

n         网络集成到Game core如果想要优化网络,沟通成本很高欧美公司优化中国网络体验实在没有信心

 

 

新架构Relay Server + Lockstep特点

新架构以帧同步服务器Relay Server)为中心,进行消息同步转发的机制, 该机制更像MMO的架构, 但是不耦合游戏逻辑,变得通用,非常适合几人十几即时竞技游戏

架构非常简单如下:

n         基本流程是:

1)        Game core不断上报操作数据给Relay Server, 30帧 Input/s

2)        Relay Server,整合各个客户端的输入,然后输出Frame数据每个客户端,30 Frame/s

3)        Game core接收Frame,拿去渲染,渲染时,接下一帧操作,继续1)循环

 

n         服务器帧同步原理

机制,各个客户端画面表现完全一致,没有预先表现比赛每个Game core不断从RelayServer获得相同的Frame每个客户端逻辑处理一致,结果一致输出的得分篮板等事件一致,这点防作弊基础

n         开发分工

1)        Game core 渲染Frame,接受操作数据,驱动下一

2)        我们 接受操作数据,在Relay Server中整合,广播Frameclient缓冲Frame然后送入Game core

 

 

为什么同步Input,而不是Action

关于同步点,可以是

n         Input: 鼠标,键盘,手柄等操作数据

n         Action: 游戏逻辑层的动作触发技能

 

对于MMO各类游戏,可以使用Action同步,因为Action时间粒度很粗,客户端可以从容的做预测拉扯表现优化。当然服务器就必然包含游戏逻辑,所以瓶颈往往不在网络IO,而在CPU。

 

而对于NBA这种即时动作游戏来说没有预定操作,完全即时碰撞,每一帧动作都是后续计算的基础,同步的时间粒度一般1帧1/30 秒这对网络优化的要求很高。

 

InputFrame的说明

n         Input是玩家的操作输入数据

如果Input按键等状态的全量, 那么可以丢失,如果是操作的增量就不能丢失了。我们采用的是状态全量,丢失Input认为操作状态改变。每个Input size并不大不足16 byte。

 

n         Frame比赛玩家的Input1帧整合

              Lockstep机制要求Frame不能丢失丢失将导致各个客户端无法计算一致,网络协议要确保不丢包。1 Frame size = N * Input size

 

Frame如何生成? 以及硬件时钟误差

每个客户端时间戳一样, 然后和服务器的时间戳也一样,这么简单的事儿, Input 1 放到 Frame1不就OKso easy,我们曾经这么想。

 

经过测试我们发现只要是电脑,时间戳都有误差。 一般万分之一以下即一万秒,会有1秒的误差,也有更糟糕的。

 

如果上述方法做, 那么一局游戏结束时,每个客户端至少造成1帧以上的延迟。

 

所以我们没有把InputFrame严格对应起来,就用最新Input放到当前Frame,过时Input丢弃带来额外的好处就是,还降低一定的操作延迟。

 

新架构疑问

1.        每个客户端上下行60个消息,服务器吃得消吗?

实际结果网络的流量比之前要节省的多, 之前每客户端N*10K Byte/s现在< 5K byte/s

2.        速度快不快?

如果玩家跨城,跨省,甚至跨ISP,此时,P2P不一定比Relay Server快P2P存在相当比例的连不通依然要转发。

 

更重要的是:

之前P2P玩家网络关系是网状,Relay server玩家的关系星状, 经验告诉我们,星状比网状更容易优化。

 

 

Relay server各种优化

TCP 还是 UDP ?

设计Relay server,发现会存在两种消息我们最初这样设计的:

n         过程控制协议, 采用TCP

n         InputFrame协议采用UDP

为什么是UDP

其他服务器的运营情况来看,网络一切都好时,TCP 表现很好,但是一旦网络不稳定,存在IP丢包,TCP的延迟会增加断线。

获得经验

n         TCP适合流量敏感的应用TCP的窗口机制可以很好的利用带宽

n         UDP适合对延迟敏感的应用

 

解决UDP丢包

当前中国普通用户平均0.2%的丢包率解决丢包根本方案

其中Relay server 30帧/s持续发送的特征 可以得到很好的利用,如果1没收到2收到,那么初步判断1丢失了,当然也可能是乱序,我们会等待100ms再判断是否要求Server重传。

网络上肯定存在很多种方案,我们也测试很多方案,包括超时重传,丢包立即重传,最终方案是这两个的结合体,只不过超时是固定时间100ms,取得结合流量和延迟的平衡

其实没有所谓的最优只有尽量合适。需求满足采用成本更低方案,明智的选择。

 

丢包重发延迟怎么?

这是丢包重发的后遗症, 1次重会增加较的延迟,至少是100ms + 2时间 + 一个ping时间

 

如果收到Frame 3 同时携带Frame 2, 那么即使之前的Frame 2即使丢了,不需要重传就可立刻补充——这就是冗余方案

 

冗余是通过流量换速度。

 

我们做了一套动态冗余算法,包含上行逻辑和下行逻辑,通过每个客户端丢包状况来动态调整冗余

 

当然,也有一些游戏,通过全冗余来做,就是在客户端还没收到ACK时,总是携带所有没有ACK数据

这种方式简单粗暴,但是会导致流量增长N各有利弊

 

下行冗余倍率图,平均在1.4倍左右。还是比较理想的。图中纵轴10000表示无冗余

 

奇怪的不一致,UDP分组长度的确定

运营过程中,发现有部分玩家经常出现游戏不一致异常结束

日志中发现,玩家Frame包校验码不正确就是说这个包是错的。

 

怎么可能网上都说UDP保证数据的正确,看来也不尽然。

后来咨询了各种资料总结问题 数据报太大,被分组,部分路由器组包错误(最大可能

 

我们不断缩小分组长度: 1400 1200 – 1000 – 576 byte

最终我们定在了internet标准MTU尺寸576 byte包含IP包头和UDP包头

这个问题就基本搞定了。

UDP回发

服务器收到UDP包后,如何回发也不太简单 这在TCP里简直不是事

收到到第一个UDP包,高兴的记下回发地址, 以为就可以一劳永逸往里面塞包回发那真是大错特错。

说实话我们开始就是这么做的,结果客户端动不动收不到回包

解决办法是:

服务器每次收到包就记下回发地址,这样中间路由如何变化,都可以送回去

真的解决了吗? 其实依然可能收不到,不过还好,客户端总是会发送数据到服务器,这样又会中间路由器中,建立了通路所以,这个办法还是可行的。

突然间,明白了统一标准的重要性,即便标准确定了执行还是另一回事,从时钟不一致、UDP组包错误这个问题,世界观开始崩溃了,以前整天写逻辑代码,还真是无法理解底层的江湖险恶。

淘汰TCP

运行过程中, 我们发现了个奇怪现象,在跨网用户中,

用户可以流畅比赛但是控制协议频繁的断开。 导致最终比赛异常结束。

 

其他服务器也有类似情况问题其实发生在TCP的拥塞退让机制,因为丢包较多嗲话TCP可能会频繁断线

咨询了一些大牛,建议我们重构TCP,当然还是TCP复杂的特性吓退了最终,我们决心淘汰Relay serverTCP协议实现了最简单的可靠UDP: 等停协议

改造后,不单连接更稳定了, 因为侦听UDP端口,连部署也更加容易了。

一个丢包的例子,这种情况UDP工作正常,TCP就容易断线。

流量的优化

Relay server的瓶颈不在于逻辑这块,而是在于部门开发的连接组件,该组件的CPU占用网络流量成正比

我们做了下面一系列优化,最终压缩后的结果,平均单用户比赛时上行1.4K byte/s, 下行 3.3 K byte/s算是个不错的结果。

 

n       压榨Input尺寸Input> 16byte

n       降频:

relay server试图从30帧/s降低到15帧/s,渲染仍然30帧以上,但是操作手感有些下降其实未采用

n       Input去重复

玩家实际操作中会有,按键并非帧都变,所以可以不上报

从上图看出,玩家实际43%操作是变化的,其余都是未变化。

算法原理摘要

 

n       间压缩

帧间压缩的概念是,玩家两次按键变化之间只是部分数据变化比如按住left+shift向左加速跑,松开shift,变为向左走步 该变化只是shift按键,数据是1个bit

具体做法当前上一帧异或这样就出现很多0值,很容易压缩

客户端还原只需要保存上一帧的数据,总是能正确还原出当前帧。

 

n       Frame压缩

在上述基础上,再进行RLE压缩进一步压榨使用RLE算法原因是速度效果的平衡

 

Relay server帧跳如何稳定

为了方便, 服务器底层循环使用公司SERVER组件的tick循环,设置为1ms触发一次,实际测试时,这个1ms很不靠谱经常超过1ms

 

所以为了确保Frame定时尽量精准循环使用clock_gettime函数不断获取时间然后frame帧跳函数中,处理所有玩家当前帧的数据方式可以说简单粗暴效率由于是30帧/s,流量上还是平滑的。

 

比较偷懒,直接贴代码了

 

Relay server性能优化,以及动态负载

 

n         一个玩家上下行60个/s,  1K用户就已经6万/s那时候 公司互娱架构组前端连接组件,也就10万/s, 怎么办?

n         处理这么多的消息包, CPU吃得消吗?

 

但我们也发现,同样的消息量下,Relay server没有任何CPU瓶颈, 这个当然受益于我们自己一套高性能架构组件。

 

主要瓶颈前端连接组件上,主要热点有两个

n         消息压解包

之前效率很慢自从前端连接组件使用了C++编解码方式这个瓶颈热点消失,强烈推荐新版本,大力赞一下动娱乐的技术架构部门

n         系统API调用

            这个问题比较戏剧化,最早我们认为同型号的服务器,性能应该差不多,这个意识直到很久以后才改变,多次运营冲高,都发现有玩家卡顿

            最终调查发现,服务器CPU 100%,原因是服务器操作系统的问题,具体原因可能是降频其他设置总之,服务器实际性能差距可以超过10倍

            无奈之下我们上了动态负载逻辑通过监控瓶颈进程CPU限制连接人数。使用之后,效果非常好。大规模集群中,动态负载是必须的。

 

动态负载的日志输出,能看到服务器性能差别还是很大的:910 –  6997

平滑渲染,Jitter buffer如何优化

收到Frame就去渲染吗?

如果收到Frame渲染 有个例子可以想象如果视频播放不缓冲,你将看到刺激的抖动画面。同理,游戏也一样, 所以一个有效的Jitter buffer至关重要

 

这个Buffer很有挑战

n         不能太早的把Frame送入Game core,否则下一帧没来怎么办

n         不能太晚Frame送入Game core,那会造成很大的操作延迟。

n         必须能适应玩家的网络情况,自动调节。

 

悲剧的是,游戏对延迟要求远远高于视频和语音

 

测试经验值NBA操作延迟150ms以上,体验将下降

 

只有网络会抖动吗

不尽然,渲染也会抖动,这帧33ms,下一帧可能就100ms

客户端和服务器的时间戳存在误差。

 

除了小的波动还有更大的抖动我们称为毛刺:

如果客户端同时启动了另外一个程序,产生CPU争用可能某一帧渲染会超过N秒

如果网络拥塞,可能导致画面卡超过N秒

 

画面流畅低延迟,你选哪一个

所谓鱼和熊掌不可兼得,想要画面流畅又要低延迟在有限条件下,这只是梦

到底是流畅重要还是低延迟重要,我们纠结了很久,这也直接决定着Jitter buffer的算法模型,以及参数设定。

 

经过用户的长期反馈,得到一个最可行理论:

画面的流畅性大于一切,要尽可能消除一切抖动, 牺牲延迟换取稳定。

 

所以 策略就是两点

n         增加延迟消除抖动

n         跳过毛刺

 

然后也就决定了如何衡量客户端体验数据

n         操作延迟大小

n         卡顿频繁程度

典型网络抖动是这样

 

 

时间戳需要修正

比赛中,所有客户端都是跟随者Relay server脚步那么问题来了,服务器的时间戳和客户端的时间戳不一致,就算发送到客户端,包含网络延迟时间

是否要进行客户端的时间戳修正呢? 看了网上众多时间同步算法 头又开始大了起来

 

最终证明,这不需要完全可以推导证明,时间戳同步本身是完全不必要的Jitterbuffer算法中全部在计算中被消掉

 

基本原理就是虽然长时间来讲,客户端和服务器时间会存在较大误差,但是我们算法中全部使用相对时间,我们相信,在网络近期采样的阶段10秒),这个误差会缩短到1ms以下,完全不产生任何影响

所以Jitterbuffer算法就直接使用 服务器和客户端的时间戳的差。可大大简化复杂度

 

Jitterbuffer算法实现

为了几十ms的延迟, 我们查遍了Google,翻遍了论文,包括我们的土著算法,我们一共研究懒散法定时修正法VoIP算法MAPDV算法MAPDV2算法SMPDV算法以及我们正在使用的MAX暂时命名

 

下面是一些算法的基本原理大同小异。

(摘自各个文章只为助于理解,并未使用

 

我们MAX算法原理是,取近期100个Frame网络延迟,取最高点之上作为Jitterbuffer的上限。估计未来只有极小可能超过大于该值,通过一些算法,消除大个毛刺。实际的Jitterbuffer上限一般浮在网络抖动曲线的上面。

该算法和其他算法的区别是,

n         取延迟上限消除一切抖动。其他算法,看起来很美,却不能很好的实现这个目的。

n         速度比其他算法,但更精确,因为一个尽量低的合理延迟,会有更好的游戏体验

n         适应所有抖动模型,因为简单粗暴。

 

MAX算法模拟实验图示参照绿线,制作模拟工具,是早期验证理论的有力武器)

 

渲染只能30帧吗?

服务器虽然限于能力帧数当然越少越好, 这样承载在线人数更高。

但是如果渲染帧太低的话,眼睛就受不了体验很差。

 

所以,我们开发了一套帧数扩展算法,能保证各个客户端接收30帧,能准确的扩展到任意帧数比如30、45、60

 

原理比较简单,利用整数计算一致Frame序号乘以一个倍数立刻变身为45或60帧同时复制补充中间Frame data。这些都在客户端进行,并且各个客户端精确一致。当然对于Game core而言,这些是透明的。

 

作弊机制

NBA既然各个客户端表现一致,那么防作弊变的简单,因为比赛的客户端产生的事件完全一致,只需要汇报到逻辑服务器(另一种服务器)上检查只要有人事件其他人不一致,则踢出。这种方式简单且有效。

传统的MMO服务器是把同步校验整合在一个服务器中我们是分开,帧同步体系,和游戏逻辑体系是完全分离的,很合适NBA等类似竞技游戏。

 

IDC持续优化

优化目的

想让更多人体验更好,延迟更低,那么只有更合理分布部署Relay server。保证Relay server和玩家很“

帧同步服务器匹配

玩家进入一个比赛,前提连接到一个帧同步服务器

怎么选择一个服务器,比赛内所有玩家的操作延迟比较低,是这个命题的目标。

这涉及个过程:

1)        收集玩家对服务器的ping

RelayServer有一个管理服务器RelayCenter负责收集ping数据

2)        匹配算法根据各个玩家IDC的延迟,选择一个IDC

我们有一个专门的匹配服务器,并增加了IDC匹配算法。当有一个IDC,比赛所有玩家该IDC的平均ping最低,认为是一个合适的IDC当然还考虑过其他算法,比如是否照顾一下延迟最差的玩家。但如果从总体来看, 平均ping最低的算法,所得到的统计数据一定最好看不是吗

3)        根据服务器负载,该IDC中选择一个RelayServer

RelayCenter也负责分配一个负载较低服务器

RelayCenter收集玩家ping

收集玩家反馈的ping非常关键,怎么收集,如何整理,都对分配的效果影响巨大

最初,我们可笑的通过比赛统计后的ping汇报来收集结果这陷入了一个糟糕正反馈循环系统:

最终导致了这个优化几乎不生效。

就好像,一直给孩子吃糖,水果什么的看,孩子只会要糖。就是这个道理。——只有信息更全面选择才能正确

 

我们是这样改进的

n         每个客户端客户端定取得IDC的ping值

n         Ping是从客户端通过UDP到relay server反馈的一个时间

n         每个玩家,针对每个IDC,都有一系列Ping统计,保存在RelayServer

n         连续多个Ping采样值进行加权平均

New ping = (old ping *(N-1) + cur ping) / N

 

通过IDC,省份,ISP这些key,可以做出ping,操作延迟,人数等各种视图

一个例子,上海市北IDC 各省市ping我们公司数据平台的数据比较。看起来很像不是吗

RelayCenter负载分配算法

RelayServer分布在多个IDC中,通过UDP协议,定时把瓶颈进程的CPU上报给RelayCenter

既然能及时的得到负载反馈, 分配算法就简化为,取负载最低的Server。

还有技巧,当不能及时的得到负载反馈时, 这种分配方式会导致这个Server快速满载,很不均衡,可改为权重随机分配方式,权重值=空闲单位数。

多线服务器的判断

多线服务器指,一个服务器同时支持电信联通移动等ISP

这样的服务器会有多张网卡,每个网卡对应一个ISP

跨网用户得到更好的体验,也能收敛游戏大区的数量聚集人气。

 

实现并不复杂客户端连接某Relay server时,需要选择效果最好的IP进行连接

我们未使用域名方式,原因智能域名不一定比我们智能更主要的是它可能会失效

最佳ISP端口判断如下:

n         Ping

连续ping 20个包,取ping值最低的服务器,该ping值算法模拟Lockstep取上限。

 

n         ISP判断

由于ping的数量不能太多,采样数量太少,使判断不精确所以辅助ISP判断。通过查表得出该IP的ISP,在ping相差不大的情况下,优先连接对口ISP的IP。由于IP配置表经常变更,这也只能时辅助手段。

 

 

线服务器体验效果

开始我们担心线服务器的游戏体验,但经过匹配优化后,

新开的混合大区效果来看, 线服务器的效果,优秀。

 

TGW体验效果

TGW是公司IP收敛方案。这里推广一下,该方案是非常棒的,重要的是,TGW资源容易申请,但很可惜确实会增加一些操作延迟不过对于那些延迟不敏感的游戏,TGW应该没什么影响。

 

用户ISP分布一些参考

 

用户省份分布

广东无疑是游戏大省

很容易看到哪些省份是电信的天下,哪个省份联通的天下

用户操作延迟分布

Y=操作延迟,X=用户采样,关注蓝线,红线请无视

这个图,很有趣可以看到80%以上用户<150ms,也有部分用户延迟很高,还是坚持打完了比赛

Relay server分布部署

经过匹配优化后,事情还没有完,要选择部署更多的IDC才能生效

部署relay server总的来说部署越多,越分布,效果越好。

但是要考虑性价比的话,还是需要找到规律

n       重点IDC明显更稳定更好

n       支持多ISP的IDC更好,即便电信游戏大区, 往往也混杂了15%左右的非电信用户。

Relay center优化

以前各个大区都有各自的relay center,管理各个大区的RelayServer,各自为政。后来整合各个大区的Relay center,配置完全使用一份。

有如下好处:

n         用户可从更多的relay选择中受益,得到更好一点的体验

n         降低运维部署成本,大量的Relayserver调整是很痛苦

 

如果再进一步,是否可以建立帧同步云

统计反馈

这是统计系统的部分统计。整个优化过程,得益于建立了完善的反馈统计体系,形成了完整的闭环。有几个关键数据大家分享

n         操作延迟:因为我们把抖动转化为延迟,所以这是最重要的质量参考数据

n         快播率:客户端因为累积太多Frame data,需要快速消耗,类似于丢弃。这是客户端渲染优化的一个重要指标。

n         STALL率客户端没有Frame data给Game core,需要等待,这个数据是反映网络毛刺的情况。

n         平均FPS同样反映客户端渲染优化情况

 

小结

网络优化是一个持续的过程,在解决问题时,作为程序,往往陷入过度追求完美方案之中但事实是,没有专家,也没有论文,就能告诉你这么做是一定对的你只有不断的试验探索,通过模型得到理论数据,通过测试获取第一批用户数据改进,再试验终究越来越接近完美但你也不能丢掉理论陷入到无休止试验之中,走到一个坑面前,记得停下来,百度一下,问问专家,或许前人留下一些线索最后,记得总结一下一些线索别人。

 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引