我如何使用 Python 視頻流將 Porn 效率提高 20 倍

已發表: 2022-03-11

介紹

色情是一個大產業。 互聯網上沒有多少網站可以與其最大玩家的流量相媲美。

處理這種巨大的流量是很困難的。 更難的是,色情網站提供的大部分內容都是由低延遲的實時視頻流組成,而不是簡單的靜態視頻內容。 但是對於所涉及的所有挑戰,我很少讀到接受它們的 python 開發人員。 所以我決定寫下我自己在工作中的經歷。

有什麼問題?

幾年前,我為世界上第 26 個(當時)訪問量最大的網站工作——不僅僅是色情行業:世界。

當時,該網站使用實時消息協議 (RTMP) 提供色情視頻流媒體請求。 更具體地說,它使用 Adob​​e 構建的 Flash Media Server (FMS) 解決方案為用戶提供實時流。 基本流程如下:

  1. 用戶請求訪問某些直播流
  2. 服務器以播放所需素材的 RTMP 會話進行回复

出於幾個原因,FMS 對我們來說不是一個好的選擇,首先是它的成本,其中包括購買兩者:

  1. 我們運行 FMS 的每台機器的 Windows 許可證。
  2. 約 4,000 美元的 FMS 特定許可證,由於我們的規模,我們不得不購買數百個(每天甚至更多)。

所有這些費用都開始上漲。 撇開成本不談,FMS 是一個缺乏產品,尤其是在其功能方面(稍後會詳細介紹)。 所以我決定放棄 FMS 並從頭開始編寫我自己的 Python RTMP 解析器。

最後,我設法使我們的服務效率提高了大約 20 倍。

入門

這涉及到兩個核心問題:首先,RTMP 和其他 Adob​​e 協議和格式不開放(即公開可用),這使得它們難以使用。 如何以您一無所知的格式反轉或解析文件? 幸運的是,在公共領域(不是由 Adob​​e 製作,而是由一個名為 OS Flash 的小組製作,現已不復存在)中進行了一些扭轉工作,我們以此為基礎。

注意:Adobe 後來發布了“規範”,其中包含的信息不超過非 Adob​​e 製作的反向 wiki 和文檔中已經披露的信息。 他們(Adobe)的規范質量低得離譜,幾乎不可能實際使用他們的庫。 此外,該協議本身有時似乎有意誤導。 例如:

  1. 他們使用 29 位整數。
  2. 它們在任何地方都包含帶有大端格式的協議頭——除了一個特定的(但未標記的)字段,它是小端。
  3. 在傳輸 9k 視頻幀時,他們以計算能力為代價將數據壓縮到更小的空間中,這幾乎沒有意義,因為它們一次可以賺回比特或字節——對於這樣的文件大小來說,收益微不足道。

其次:RTMP 是高度面向會話的,這使得對傳入流進行多播幾乎是不可能的。 理想情況下,如果多個用戶想要觀看同一個直播流,我們可以將它們傳回指向正在播放該流的單個會話(這將是多播視頻流)。 但是使用 RTMP,我們必須為每個想要訪問的用戶創建一個全新的流實例。 這完全是浪費。

三位用戶演示了多播視頻流解決方案和 FMS 流問題之間的區別。

我的多播視頻流解決方案

考慮到這一點,我決定將典型的響應流重新打包/解析為 FLV“標籤”(其中“標籤”只是一些視頻、音頻或元數據)。 這些 FLV 標籤可以毫無問題地在 RTMP 中傳播。

這種方法的好處:

  1. 我們只需要重新打包一次流(由於缺乏上述規範和協議怪癖,重新打包是一場噩夢)。
  2. 我們可以通過簡單地為它們提供一個 FLV 標頭來重用客戶端之間的任何流而幾乎沒有問題,而指向 FLV 標記的內部指針(以及某種偏移量以指示它們在流中的位置)允許訪問內容。

我開始使用我當時最熟悉的語言進行開發:C。隨著時間的推移,這個選擇變得很麻煩; 所以我在移植我的 C 代碼時開始學習 Python 的基礎知識。 開發進程加快了,但是在幾次demo之後,我很快就遇到了資源枯竭的問題。 Python 的套接字處理並不意味著處理這些類型的情況:具體來說,在 Python 中,我們發現自己在每個操作中進行多個系統調用和上下文切換,從而增加了大量的開銷。

提高視頻流性能:混合 Python、RTMP 和 C

在對代碼進行分析之後,我選擇將性能關鍵函數移到一個完全用 C 編寫的 Python 模塊中。這是相當低級的東西:具體來說,它利用內核的 epoll 機制來提供對數增長順序.

在異步套接字編程中,有一些工具可以為您提供給定套接字是否可讀/可寫/錯誤填充的信息。 過去,開發人員使用 select() 系統調用來獲取此信息,但擴展性很差。 Poll() 是 select 的更好版本,但它仍然不是很好,因為您必須在每次調用時傳入一堆套接字描述符。

Epoll 非常棒,因為您只需註冊一個套接字,系統就會記住這個不同的套接字,並在內部處理所有的細節。 因此,每次調用都沒有參數傳遞開銷。 它還可以更好地擴展並且只返回您關心的套接字,這比運行 100k 套接字描述符列表以查看它們是否有帶有位掩碼的事件要好得多——如果您使用其他解決方案,您需要這樣做。

但是為了性能的提升,我們付出了代價:這種方法遵循了與以前完全不同的設計模式。 該站點以前的方法是(如果我沒記錯的話)一個阻塞接收和發送的單一進程; 我正在開發一個事件驅動的解決方案,所以我不得不重構其餘的代碼以適應這個新模型。

具體來說,在我們的新方法中,我們有一個主循環,它按如下方式處理接收和發送:

Python 視頻流解決方案結合了 RTMP、FLV“標籤”和多播視頻流。

  1. 接收到的數據(作為消息)向上傳遞到 RTMP 層。
  2. 剖析 RTMP 並提取 FLV 標籤。
  3. FLV 數據被發送到緩沖和多播層,該層組織流並填充發送方的低級緩衝區。
  4. 發送者為每個客戶端保留一個結構,帶有最後發送的索引,並嘗試向客戶端發送盡可能多的數據。

這是一個滾動的數據窗口,其中包括一些啟發式方法,以在客戶端接收速度太慢時丟幀。 事情進展得很好。

系統級、架構和硬件問題

但是我們遇到了另一個問題:內核的上下文切換正在成為一種負擔。 因此,我們選擇僅每 100 毫秒寫入一次,而不是立即寫入。 這聚合了較小的數據包並防止了上下文切換的爆發。

也許更大的問題在於服務器架構領域:我們需要一個負載平衡和故障轉移功能的集群——由於服務器故障而失去用戶並不好玩。 起初,我們採用了獨立導演的方法,其中指定的“導演”將嘗試通過預測需求來創建和銷毀廣播公司的提要。 這非常失敗。 事實上,我們嘗試的一切都失敗了。 最後,我們選擇了一種相對蠻力的方法,在集群的節點之間隨機共享廣播器,從而使流量相等。

這行得通,但有一個缺點:雖然一般情況處理得很好,但當網站上的每個人(或不成比例的用戶)觀看一個廣播時,我們看到了糟糕的表現。 好消息:這在營銷活動之外永遠不會發生。 我們實施了一個單獨的集群來處理這種情況,但事實上,我們認為為了營銷工作而危害付費用戶的體驗是沒有意義的——事實上,這並不是真正的情況(儘管處理所有可以想像的情況會很好案子)。

結論

最終結果的一些統計數據:集群上的每日流量在高峰期約為 10 萬用戶(60% 負載),平均約為 5 萬。 我管理了兩個集群(HUN 和 US); 他們每個人處理大約 40 台機器來分擔負載。 集群的總帶寬約為 50 Gbps,在峰值負載時它們使用的帶寬約為 10 Gbps。 最後,我成功地輕鬆推出了 10 Gbps/機器; 理論上1 ,這個數字可能高達 30 Gbps/機器,這意味著大約 30 萬用戶同時從一台服務器觀看流。

現有的 FMS 集群包含 200 多台機器,這些機器可以被我的 15 台取代——其中只有 10 台可以做任何實際工作。 這給了我們大約 200/10 = 20 倍的改進。

可能我從 Python 視頻流項目中最大的收穫是,我不應該讓自己被必須學習新技能的前景所阻止。 特別是 Python、轉碼和麵向對象編程,這些都是我在接手這個多播視頻項目之前所擁有的非專業經驗。

那,以及滾動你自己的解決方案可以付出高昂的代價。

1後來,當我們將代碼投入生產時,我們遇到了硬件問題,因為我們使用的是舊的 sr2500 Intel 服務器,由於 PCI 帶寬低,無法處理 10 Gbit 以太網卡。 相反,我們在 1-4x1 Gbit 以太網綁定中使用它們(將多個網絡接口卡的性能聚合到一個虛擬卡中)。 最終,我們得到了一些較新的 sr2600 i7 英特爾,它們提供 10 Gbps 的光學性能,沒有任何性能問題。 所有預計的計算均參考此硬件。