色情业是个大行业。互联网上没有几多网站的流量能和最大的色情网站相对抗。
要搞定这庞大的流量很难。更坚苦的是,在色情网站上提供的许多内容都是低延迟的及时流媒体而不是简朴的静态视频。可是对付所有遇到过的挑战,我很少看到有搞定过它们的开拓人员写的对象。所以我抉择把本身在这方面的履历写出来。
问题是什么?
几年前,我正在为其时全世界会见量排名26的网站事情 — 这里不是说的色情网站排名,而是全世界排名。
其时,该网站通过RTMP(Real Time Messaging protocol)协议响应对色情流媒体的请求。更详细地说,它利用了Adobe的FMS(Flash Media Server)技能为用户提供及时流媒体。根基进程是这样的:
用户请求会见某个及时流媒体
处事器通过一个RTMP session响应,播放请求的视频片断
因为某些原因,FMS对我们并不是一个好的选择,首先是它的本钱,包罗了购置以下两者:
为每一台运行FMS的处事器购置Windows的版权
约莫4000美元一个的FMS特定版权,由于我们的局限,我们必需购置的版权量数以百计,并且天天都在增加。
所有这些用度开始不绝累积。撇开本钱不提,FMS也是一个较量挫的产物,出格是在它的成果方面(我过一会再具体说这个问题)。所以我抉择丢弃FMS,本身从新开始写一个本身的RTMP理会器。
最后,我终于把我们的处事效率晋升了约莫20倍。
开始
这里涉及到两个焦点问题:首先,RTMP和其他的Adobe协议合名目都不是开放的,这就很难利用它们。要是对文件名目都一无所知,你如何能对它进 行反向工程可能理会它呢?幸运的是,有一些反向工程的实验已经在果真规模呈现了(并不是Adobe出品的,而是osflash.org,它破解了一些协 议),我们的事情就是基于这些成就。
注:Adobe厥后宣布了所谓的“规格说明书”,比起在非Adobe提供的反向工程wiki和文档中披露的内容,这个说明书里也没有啥新对象。他们 给的规格说明书的质量之低劣到达了谬妄的田地,近乎不行能通过该说明书来利用它们的库。并且,协议自己看起来经常也是有意做成具有误导性的。譬喻:
他们利用29字节的整形数。
他们在协议头上所有处所都回收低地点存放最高有效字节(big endian)的名目,除了在某一个字段(并且未标明)上回收低地点存放最低有效字节(little endian)的名目。
他们在传输9K的视频时,不吝淹灭计较本领去压缩数据淘汰空间,这根基上是没意义的,因为他们这么折腾一次也就是淘汰几位或几个字节,对这样的一个文件巨细可以忽略不计了。
尚有,RTMP是高度以session为导向的,这使得它根基上不行能对流举办组播。抱负状态下,假如多个用户要求寓目同一个及时视频流,我们可以 直接向他们传回指向单个session的指针,在该session里传输这个视频流(这就是组播的观念)。可是用RTMP的话,我们必需为每一个要求会见 特定流的用户建设全新的一个实例。这是完全的挥霍。
我的办理步伐
想到了这些,我抉择把典范的响应流从头打包息争析为FLV“标签”(这里的“标签”指某个视频、音频可能元数据)。这些FLV标签可以在RTMP下顺利地传输。
这样一个要领的长处是:
我们只需要给流从头打包一次(从头打包是一个恶梦,因为缺少规格说明,尚有前面说到的恶心协议)。
通过套用一个FLV头,我们可以在客户端之间顺畅地重用任何流,而用内部的FLV标签指针(配以某种声明其在流内部确切位置的位移值)就可以会见到真正的内容。
我一开始用我其时最熟悉的C语言举办开拓。一段时间后,这个选择变得贫苦了,所以我开始进修Python并移植我的C代码。开拓进程加速了,但在做 了一些演示版本后,我很快碰着了资源枯竭的问题。Python的socket处理惩罚并不适合处理惩罚这些范例的环境,详细说,我们发此刻本身的Python代码 里,每个action都举办了多次系统挪用和context切换,这增加了庞大的系统开销。
改造机能:殽杂利用Python和C
在对代码举办梳理之后,我选择将机能最要害的函数移植到内部完全用C语言编写的一个Python模块中。这根基是底层的对象,详细地说,它操作了内核的epoll机制提供了一个O(log n)的算法巨大度。
在异步socket编程方面,有一些机制可以提供有关特定socket是否可读/可写/堕落之类的信息。已往,开拓人员们可以用select()系 统挪用获取这些信息,但很难大局限利用。Poll()是更好的选择,但它仍然不足好,因为你每次挪用的时候都要通报一大堆socket描写符。
#p#分页标题#e#
Epoll的神奇之处在于你只需要挂号一个socket,系统会记着这个特定的socket并处理惩罚所有内部的混乱的细节。这样在每次挪用的时候就没 有通报参数的开销了。并且它合用的局限也大有可观,它只返回你体贴的那些socket,对比用其他技能时必需从10万个socket描写符列内外挨个查抄 是否有带字节掩码的事件,其优越性真长短同小可啊。
不外,为了机能的提高,我们也支付了价钱:这个要领回收了完全和以前差异的设计模式。该网站以前的要领是(假如我没记错的话)单个原始历程,在吸收和发送时会阻塞。我开拓的是一套事件驱动方案,所觉得了适应这个新模子,我必需重构其他的代码。
详细地说,在新要领中,我们有一个主轮回,它按如下方法处理惩罚吸收和发送:
吸收到的数据(作为动静)被通报到RTMP层
RTMP包被理会,从中提取出FLV标签
FLV数据被传输到缓存和组播层,在该层对流举办组织并填充到底层传输缓存中
发送措施为每个客户端生存一个布局,包括了最后一次发送的索引,并尽大概多地向客户端传送数据
这是一个转动的数据窗口,并包括了某些试探性算法,当客户端速度太慢无法吸收时会扬弃一些帧。总体来说运行的很好。
系统层级,架构和硬件问题
可是我们又碰着别的一个问题:内核的context切换成为了一个承担。功效,我们选择每100毫秒发送一次而不是及时发送。这样可以把小的数据包汇总起来,也制止了context切换的爆炸式呈现。
也许更大的一个问题在于处事器架构方面:我们需要一个具备负载平衡和容错本领的处事器集群,究竟因为处事器成果异常而失去用户不是件好玩的工作。一 开始,我们回收了专职总管处事器的要领,它指定一个”总管“认真通过预测需求来发生和消除播放流。这个要领富丽丽地失败了。实际上,我们实验过的每个要领 都相当明明地失败了。最后,我们回收了一个相对暴力的要领,在集群的各个节点之间随机地共享播放的流,使流量根基均衡了。
这个要领是有效的,可是也有一些不敷:固然一般环境下它处理惩罚的很好,我们也遇到了当所有网站用户(可能相当大比例的用户)寓目单个广播流的时候,性 能会变得很是糟糕。好动静是,除了一次市场宣传勾当(marketing campaign)之外,这种环境再也没呈现过。我们陈设了别的一套单独的集群来处理惩罚这种环境,但真实的环境是我们先阐明白一番,以为为了一次市场勾当而 牺牲付用度户的体验是说不外去的,实际上,这个案例也不是一个真实的事件(固然说能处理惩罚所有想象获得的环境也是很好的)。
结论
这里有最后功效的一些统计数字:天天在集群里的流量在峰值时是约莫10万用户(60%负载),平均是5万。我打点了2个集群(匈牙利和美国),每个 里有约莫40台处事器配合包袱这个负载。这些集群的总带宽约莫是50 Gbps,在负载到达峰值时约莫利用了10 Gbps。最后,我尽力做到了让每台处事器轻松地能提供10 Gbps带宽,也就便是一台处事器可以遭受30万用户同时寓目视频流。
已有的FMS集群包括了高出200台处事器,我只需要15台就可以代替他们,并且个中只有10台在真正提供处事。这就便是200除以10,便是20 倍的机能提高。或许我在这个项目里最大的收获就是我不该让本身受阻于进修新技术的坚苦。详细说来,Python、转码、面向工具编程,这些都是我在做这个 项目之前缺少专业履历的观念。
这个信念,以及实现你本身的方案的信心,会给你带来很大的回报。
【1】厥后,当我们把新代码投入出产,我们又碰着了硬件问题,因为我们利用老的sr2500 Intel架构处事器,由于它们的PCI总线带宽太低,不能支持10 Gbit的以太网卡。没辙,我们只好把它们用在1-4×1 Gbit的以太网池中(把多个网卡的机能汇总为一个虚拟网卡)。最终,我们得到了一些更新的sr2600 i7 Intel架构处事器,它们通过光纤到达了无机能损耗的10 Gbps带宽。所有上述汇总的功效都是基于这样的硬件条件来计较的。