英雄联盟(League of Legends )不是一个秒级游戏,而是一个毫秒级游戏。在日常的生活中,2秒的流逝可能丝毫不会引起你注意——你已经花了2秒读到这了!但在游戏中,2秒的眩晕效果可能让你“度秒如年”。在LOL中的每一场单人匹配赛中,数以千计的以毫秒为单位发出的命令决定了哪个队能“力压群雄,独占鳌头”,并确定“荣誉对手(honorable opponent)”得分点。
我是一名Riot的网络工程师,也是Riot Direct team中的一员,非常着迷于这种毫秒级的应用。如果玩家电脑与服务器间的通信变得缓慢,那么玩家就不能在游戏中正常地做出游戏指令——这很可能让你的团队仅仅因为游戏连接问题而最终走向失败。我所在的Riot Direct team团队的使命就是尽可能地为玩家提供最快的游戏网络连接。
这是三部曲中的第一分部分讨论关于网络游戏使用商用因特网对玩家的交付机制所面临的问题、Riot是怎样处理这些问题以及对未来实时应用发展的展望。
在这篇文章中,我将深入探讨如何运用因特网,使用一种令人惊艳的技术,而不是构建一个专门的实时应用。如果你的浏览器向rottentomatoes.com发出一个GET请求查找最新的邦德(Bond)电影影评,你可能不介意等个几秒钟如果所有相关的信息都呈现了。以下的这种情况不会出现:rottentomatoes.com的某个用户想在十分之一秒就能获得某电影的影评。但是,当我在玩英雄联盟的时候,就需不断地吞噬信息,并且需要保证链接速度足够快。
我还将讨论一些因特网组件的建设和什么影响这些组件的表现。另外,我会用rottentomatoes.com 例子来更深层次的说明这个问题:为什么网络游戏通信从根本上不同于其他应用程序。最后,讲一下为什么因特网上的路由硬件加剧了实时通信的这个问题。
一系列的链路管道(A SERIES OF TUBES)
因特网是一个了不起的工具。它是能够分发海量数据从而造就了多彩的现代生活。它催生新的商业生态系统、新的通信方式和新的游戏思维。然而,在某些方面,它开始到达它的局限性。基于用户体验的应用程序,需要实时响应时间 (例如网络游戏),那由于因特网本身的设计缺陷,使这些实时应用体验糟糕。
因特网并不是一个统一的系统,而是由一个多个实体堆集的系统。当你打英雄联盟时,数据从Riot的服务器转移到骨干网(如Level3,Zayo 和Cogent) 然后再到ISP运营商 (像Comcast,AT&T、Time Warner Cable和Verizon),最后再到你的主机上,反之亦然。
骨干网通过光纤电缆与ISP运营商内联,这些线缆与列车轨道相邻,迂迂回回跨越整个国家,并利用BGP协议(边界网关协议)来传输流量。不幸运的是,这种协议理论上是将流量在最优的路径上从一个方传送另一方,但实际却走的是迂回的链路路径。罪魁祸首就是:经济学(商业价值与成本驱动)。骨干网和ISP运营商传送流量时选的是最低成本的路径,而不是某个潜在最短路径。换句话说,数据包从A点到达Z点,这些公司仅仅在意的是选一条最便宜的路径,而不是选一条最快的路径。
下面举个例子,现在有一个LOL玩家提交了一个数据包,如下图所示,这个数据包本来是直接在San Francisco 和Portland之间传送的。但是,它却是从San Francisco到Los Angeles,再到Denver,再到Seattle,然后最终到达目的地Portland。若是直接走的话,花费14ms,但迂回的走将花费整整70ms。时间花费陡然增加了500%。买手机靓号平台对于骨干网和ISP运营商来说这或许能接受,但对于游戏来说就不能接受了,比如LOL。
对于LOL这种实时应用来说,这种迂回的流量选路会导致悬殊的用户体验,特别是因为不同的ISP和骨干网提供商允许不同的路线。因此,一个玩家在家可能玩网络游戏享受极低的延迟,然而他另一个朋友,就住在隔壁,但是由于选择的是不同的ISP运营商,带来的是极大的延迟。
要是我们能缓冲(IF ONLY WE COULD BUFFER)
当前的互联网体系结构非常适合视频流媒体,就像Netflix,这种架构能够缓冲内容以减轻延迟带来的影响。如果缓冲区缓冲了五秒的视频,随后的数据可以在五秒之上到达。事实上,对于像 Netflix的应用程序,缓冲意味着用户流量能穿越地球 100 次并且用户体验会和使用了最短路径传送流量一样好。不过在LOL游戏中,未来的某种操作不能被缓冲,因为玩家还未玩到那一步,因此五秒钟是完全不能接受的。
路由器也爱莫能助(ROUTERS AREN'T HELPING)
最后,除了高延迟和无法缓存,网络路由器——因特网上的主要硬件——加剧了实时应用所面临的问题。那么需要解决的问题就是如何与线缆上传输的数据包交互。
绝大多数的互联网流量被分解成若干1500个字节大小的数据包:考虑到在因特网上特有的tractor-trailers包分解模式,几乎每个图片、 影片或歌曲在线传输发送时都使用这种专门大小的数据包。(以太网version2版的最大传输单元是1500字节。有兴趣的读者可以在这里查看的实际帧的最大传输单元的大小)形成鲜明对比的是,游戏包流量通常是55字节大小。游戏通信需要不断的更新,但每个单独的数据通信包却是非常小的。当Netflix向用户传输大量高质量的视频流量帧时,一个LOL玩家传送的数据流量包也不过是玩家在地图上右击的位置信息。
路由器的问题源于处理开销,路由器必须对每一个不同大小的数据包进行处理。影响路由器处理效率并不是数据包的大小,而是数量:因此,若两个小数据包所携带的信息与一个大数据包所携带的信息量一样,但传送相同信息量时分成2个小数据包处理开销相比处理一个大数据包的花销增加了一倍,而丝毫没有降低的趋势。对于英雄联盟,恒定的55字节数据包,相较于1500字节标准数据包,在处理花销上增加了27倍。这是巨大的!
如果路由器得到的数据包超过它可以处理的能力,那么路由器就不得不丢弃一些数据包。那么那些被丢弃的包不复存在,这就导致了极为糟糕游戏体验:其他玩家在屏幕上漂移,意外的被攻击,毫无缘由的掉血。在这2个特定的方式中,由游戏发送的数据包的大量增加(与最初因特网流量包类型设计目的相比)反而加重了路由器丢弃包情形。
首先要处理的就是路由输入缓冲区,在那里数据包暂时停滞以防止路由器因为任务过重而崩掉。即便这个缓冲区容量有一个固定的大小,比如说,10MB,它也只能容纳固定的数目的大小无关的数据包。一旦缓冲区已满,路由器只能把后到的数据包丢弃。因为英雄联盟这个游戏以27倍速率产生数据包,那路由器缓冲区被填满的速率那也是原来的27倍。对此感兴趣的读者可以点击此学术论文:出自Stanford 学者的"Sizing Router Buffers"来获取更多信息。
网络游戏业务的第二个问题是游戏流量处理机制,许多(但不是全部)路由器对于TCP包和UDP包处理方式是不一样的。大多数游戏,基于UDP包传输会增加效率和速度。然而,当因特网上的路由器由于数据包拥堵造成网络不堪重负时,许多路由器会直接忽略UDP数据包。这些UDP数据包会被立即丢弃,永久不能恢复。
这是因为TCP是保障可靠性的,而UDP却不保障可靠性。如果TCP包无法到达时,接收方将创建一个新的数据包,并将其发送到原始发送者以请求丢失的信息。所有这一切都保证了TCP发送的消息最终将会被接收方完整接收。互联网上的路由器都意识到TCP这个特点,而且在网络不堪重负的时候,丢弃一个TCP数据包只会立即导致客户端创建一个新的数据包。总之,就在在整个网络系统减负上无计可施时,丢弃一个UDP报文却能减轻负载。请注意,利用UDP报文也能实现可靠连接(网络游戏中经常使用某种结构来使UDP报文有序可靠传输),但是路由器却不能分清哪些是提供可靠的服务UDP报文,哪些不是。
考虑到这一切,因为使用的是BGP协议,一个短距离的数据传输可能最终是由经过很多路由器构成的迂迂回回的路径来进行通信。如果直接(或者说最简单)的路线是由起点到终点路由器构成,那么一个数据包经过了2个路由器。如果在BGP 协议下,一个数据包中间进行了4跳,这个数据包总共经过了6个路由器,那就意味着系统整体负载增加了三倍。也正是我们使用这个协议使得路由器不堪重负。
结语WRAPPING UP
所有这一切都在一起加起来看,英雄联盟想要实现真正的即时对战,因特网并不是一个很好的选
择:对于一个毫秒级响应要求的网络游戏,高延迟和高丢包率带来了糟糕的游戏体验。实时游戏的数据传输,需要一种新方法——减少迂回连接的光纤线路,BGP,以及路由器配置所导致的低效率。在我的下一篇文章中,我将详尽的阐述一种新方法,这种方法已经在Riot Direct team中被运用去解决这些问题,然后分享这种方法的原理,以及我们运用这个方法想提升什么。