作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
色情是一种 大工业. 互联网上没有多少网站可以与它最大的竞争者的流量相媲美.
应付如此庞大的交通是很困难的. 让事情变得更加困难, 色情网站提供的大部分内容都是由低延迟的实时视频流组成,而不是简单的静态视频内容. 但对于所有涉及的挑战,我很少读到关于 python开发人员 谁来对付他们?. 所以我决定写一下我自己的工作经历.
几年前,我是 工作 这是世界上访问量最大的第26个网站(当时)——不仅仅是色情行业,而是整个世界.
当时, 该网站通过实时消息协议(RTMP)提供色情视频流请求。. 更具体地说, 它使用了闪光 Media Server (FMS)解决方案, 由Adobe公司建造, 为用户提供直播. 基本流程如下:
有几个原因, FMS对我们来说不是一个好的选择, 先从成本说起, 其中包括购买两者:
所有这些费用开始逐渐增加. 撇开成本不谈,FMS是一款缺乏的产品,尤其是在功能方面(稍后会详细介绍)。. 所以我决定放弃FMS,从头开始编写自己的Python RTMP解析器.
最后,我设法使我们的服务效率提高了大约20倍.
这里涉及两个核心问题:首先, RTMP和其他Adobe协议和格式未打开(i.e.(可公开获取),这使得它们很难与之合作. 如何以您一无所知的格式反转或解析文件? 幸运的是, 在公共领域有一些逆转的努力(不是由Adobe制作的), 而是由一个叫做OS 闪光的组织开发的, (现在已经不存在了)我们工作的基础.
注:Adobe后来发布了“规范,其中包含的信息并不比非adobe制作的反向维基和文档中已经披露的更多. 他们(Adobe)的规范质量低得离谱,却成功了 几乎不可能真正使用他们的库. 此外,协议本身有时似乎是有意误导. 例如:
其次:RTMP是高度面向会话的, 这使得传入流实际上不可能多播. 在理想的情况下, 如果多个用户想观看同一个直播流, 我们可以将它们传递回指针,指向正在播放流的单个会话(这将是多播视频流)。. 但是对于RTMP, 我们必须为每个想要访问的用户创建一个全新的流实例. 这完全是浪费.
考虑到这一点,我决定将典型的响应流重新打包/解析为 FLV“标签” (其中“标签”只是一些视频,音频或元数据). 这些FLV标签可以在RTMP中传播,没有什么问题.
这种方法的好处:
我开始使用当时我最熟悉的语言:C进行开发. 随着时间的推移, this choice became cumbersome; so I started learning the basics of Python while porting over my C code. 开发过程加快了,但在几个演示之后,我很快就遇到了 资源枯竭问题. Python的套接字处理并不是为了处理这些类型的情况:特别是, 在Python中,我们发现每个动作需要进行多个系统调用和上下文切换, 增加了大量的开销.
在分析代码之后, 我选择将性能关键函数转移到完全用C编写的Python模块中. 这是相当低级的东西:具体来说,它利用了内核的 epoll 提供对数增长顺序的机制.
在异步套接字编程中,有一些工具可以为您提供有关给定套接字是否可读/可写/错误填充的信息. 在过去, 开发人员使用select()系统调用来获取这些信息, 规模很糟糕. Poll()是select的更好版本, 但它仍然不是那么好,因为你必须在每次调用时传递一堆套接字描述符.
Epoll很神奇,因为你所要做的就是注册一个套接字,系统就会记住那个独特的套接字, 内部处理所有细节. 因此,每次调用都没有传递参数的开销. 它还具有更好的可伸缩性,并且只返回您关心的套接字, 比起运行一个包含100k套接字描述符的列表来查看它们是否具有带位掩码的事件(如果您使用其他解决方案,则需要这样做),哪一种方式更好.
而是为了提高性能, 我们付出了代价:这种方法遵循了与以前完全不同的设计模式. The site’s previous approach was (if I recall correctly) one monolithic process which blocked on receiving and sending; I was developing an event-driven solution, 所以我不得不重构剩下的代码来适应这个新模型.
具体地说, 在我们的新方法中, 我们有一个主循环, 其接收和发送处理如下:
这是一个滚动的数据窗口, 并且包含了当客户端接收太慢时丢弃帧的一些启发式方法. 一切都很顺利.
但是我们遇到了另一个问题:内核的问题 上下文切换 我们变成了负担. 因此,我们选择每100毫秒写入一次,而不是立即写入一次. 这就聚集了较小的数据包,并防止了上下文切换的爆发.
也许更大的问题存在于服务器体系结构领域:我们需要一个负载平衡和故障转移功能的集群——由于服务器故障而丢失用户并不有趣. 起初, 我们采用了独立董事的方式, 在这种情况下,一个指定的“导演”会试图通过预测需求来创造和破坏广播节目. 但这次失败了. 事实上,我们所尝试的一切都失败了. 最后, 我们选择了一种相对蛮力的方法,在集群的节点之间随机共享广播器, 等于流量.
这个工作, 但有一个缺点:虽然一般情况下处理得很好, 当网站上的每个人(或不成比例的用户)都观看一个广播时,我们看到了糟糕的表现. 好消息是:这种情况绝不会发生在营销活动之外. 我们实现了一个单独的集群来处理这个场景, 但事实上,我们认为为了营销而破坏付费用户的体验是毫无意义的, 这并不是一个真正的场景(尽管处理所有可能的情况会很好)。.
来自最终结果的一些统计数据:集群上的日流量在峰值时约为10万用户(负载为60%), 平均~50k. I managed two clusters (HUN and US); each of them handled about 40 machines to share the load. 集群的聚合带宽约为50 Gbps, 他们在峰值负载时使用了大约10gbps. 最后, I managed to push out 10 Gbps/machine easily; theoretically1, 这个数字可能会高达每台机器30 Gbps, 这意味着大约有30万用户同时从一台服务器上观看流媒体.
现有的FMS集群包含200多台机器, 这些可以被我的15个代替,只有10个能做实际工作. 这给了我们大约200/10 = 20倍的改进.
也许我从Python视频流项目中得到的最大收获是,我不应该让自己被必须学习新技能的前景所阻止. 特别是, Python, 代码转换, 以及面向对象编程, 在承担这个多播视频项目之前,我的所有概念都是非常专业的吗.
这一点,以及推出自己的解决方案可以带来巨大的回报.
1 晚些时候, 当我们将代码投入生产时, 我们遇到了硬件问题, 因为我们使用的是老式的sr2500英特尔服务器,由于其PCI带宽较低,无法处理10 Gbit以太网卡. 而不是, 我们在1-4x1 Gbit以太网绑定中使用它们(将几个网络接口卡的性能聚合到一个虚拟卡中). 最终, 我们有一些较新的sr2600 i7英特尔, 在没有任何性能问题的情况下通过光学传输10gbps. 所有预测的计算都涉及到这个硬件.
世界级的文章,每周发一次.
世界级的文章,每周发一次.