Pythonビデオストリーミングでポルノを20倍効率的にした方法

公開: 2022-03-11

イントロ

ポルノは大きな産業です。 最大のプレーヤーのトラフィックに匹敵するインターネット上のサイトは多くありません。

そして、この膨大なトラフィックをジャグリングするのは難しいです。 さらに難しいことに、ポルノサイトから提供されるコンテンツの多くは、単純な静的ビデオコンテンツではなく、低遅延のライブビデオストリームで構成されています。 しかし、関連するすべての課題について、それらを引き受けるPython開発者について読んだことはめったにありません。 そこで、自分の仕事の経験について書くことにしました。

どうしたの?

数年前、私はポルノ業界だけでなく、世界で26番目(当時)に最も訪問されたWebサイトで働いていました。

当時、このサイトはリアルタイムメッセージングプロトコル(RTMP)を使用してポルノビデオストリーミングリクエストを処理していました。 具体的には、アドビが構築したFlash Media Server(FMS)ソリューションを使用して、ユーザーにライブストリームを提供しました。 基本的なプロセスは次のとおりです。

  1. ユーザーがライブストリームへのアクセスをリクエストしている
  2. サーバーは、目的の映像を再生するRTMPセッションで応答します

いくつかの理由から、FMSは、両方の購入を含むコストから始めて、私たちにとって良い選択ではありませんでした。

  1. FMSを実行したすべてのマシンのWindowsライセンス。
  2. FMS固有のライセンスは約4,000ドルで、規模が大きいため、そのうち数百(およびそれ以上)を毎日購入する必要がありました。

これらの料金はすべて上昇し始めました。 コストはさておき、FMSは、特にその機能の点で不足している製品でした(これについては後で詳しく説明します)。 そこで、FMSを廃棄して、独自のPythonRTMPパーサーを最初から作成することにしました。

結局、私は私たちのサービスをおよそ20倍効率的にすることができました。

入門

関係する2つの主要な問題がありました。1つは、RTMPおよびその他のAdobeプロトコルとフォーマットが公開されておらず(つまり、公開されていない)、操作が困難でした。 何も知らない形式のファイルをどのように反転または解析できますか? 幸いなことに、私たちの仕事の基礎となった公共圏(Adobeによって作成されたのではなく、OS Flashと呼ばれるグループによって作成された)で利用可能ないくつかの逆転の取り組みがありました。

注:アドビは後に、アドビが作成していない反転ウィキおよびドキュメントですでに開示されている情報よりも多くの情報を含まない「仕様」をリリースしました。 彼らの(Adobeの)仕様はとてつもなく低品質であり、実際にライブラリを使用することはほぼ不可能でした。 さらに、プロトコル自体が意図的に誤解を招くように見えることがありました。 例えば:

  1. 彼らは29ビット整数を使用しました。
  2. それらには、リトルエンディアンであった特定の(まだマークされていない)フィールドを除いて、どこにでもビッグエンディアン形式のプロトコルヘッダーが含まれていました。
  3. 彼らは、9kビデオフレームを転送するときに計算能力を犠牲にしてデータをより少ないスペースに絞り込みましたが、一度にビットまたはバイトを取り戻していたため、ほとんど意味がありませんでした。このようなファイルサイズではわずかな増加です。

そして第二に、RTMPは非常にセッション指向であるため、着信ストリームをマルチキャストすることは事実上不可能でした。 理想的には、複数のユーザーが同じライブストリームを視聴したい場合は、そのストリームが放送されている単一のセッションへのポインターを戻すことができます(これはマルチキャストビデオストリーミングになります)。 しかし、RTMPでは、アクセスを必要とするすべてのユーザーに対して、まったく新しいストリームのインスタンスを作成する必要がありました。 これは完全な無駄でした。

マルチキャストビデオストリーミングソリューションとFMSストリーミングの問題の違いを示す3人のユーザー。

私のマルチキャストビデオストリーミングソリューション

そのことを念頭に置いて、私は典型的な応答ストリームをFLVの「タグ」に再パッケージ化/解析することにしました(「タグ」はビデオ、オーディオ、またはメタデータの一部です)。 これらのFLVタグは、ほとんど問題なくRTMP内を移動できます。

このようなアプローチの利点:

  1. ストリームを再パッケージ化する必要があるのは1回だけでした(上記で概説した仕様とプロトコルの癖がないため、再パッケージ化は悪夢でした)。
  2. FLVヘッダーをクライアントに提供するだけで、問題の少ないクライアント間でストリームを再利用できます。一方、FLVタグへの内部ポインター(ストリーム内のどこにあるかを示す何らかのオフセットとともに)は、コンテンツ。

私は当時最もよく知っていた言語で開発を始めました。C。時間が経つにつれて、この選択は面倒になりました。 そこで、Cコードを移植しながら、Pythonの基本を学び始めました。 開発プロセスはスピードアップしましたが、いくつかのデモの後、私はすぐにリソースを使い果たすという問題に遭遇しました。 Pythonのソケット処理は、これらのタイプの状況を処理することを目的としていませんでした。具体的には、Pythonでは、アクションごとに複数のシステムコールとコンテキストスイッチを作成し、大量のオーバーヘッドを追加していることがわかりました。

ビデオストリーミングパフォーマンスの向上:Python、RTMP、およびCの混合

コードをプロファイリングした後、パフォーマンスが重要な関数を完全にCで記述されたPythonモジュールに移動することを選択しました。これはかなり低レベルのものでした。具体的には、カーネルのepollメカニズムを利用して、対数的な成長順序を提供しました。 。

非同期ソケットプログラミングには、特定のソケットが読み取り可能/書き込み可能/エラーで埋められているかどうかに関する情報を提供できる機能があります。 これまで、開発者はselect()システムコールを使用してこの情報を取得していましたが、これは拡張性が高くありません。 Poll()はselectの優れたバージョンですが、呼び出しごとに多数のソケット記述子を渡す必要があるため、それでもそれほど優れていません。

あなたがしなければならないのはソケットを登録することだけであり、システムはその別個のソケットを記憶し、すべてのザラザラした詳細を内部で処理するので、Epollは素晴らしいです。 したがって、呼び出しごとに引数を渡すオーバーヘッドはありません。 また、スケーリングがはるかに優れており、関心のあるソケットのみを返します。これは、100kソケット記述子のリストを実行して、ビットマスクを含むイベントがあるかどうかを確認するよりもはるかに優れています。これは、他のソリューションを使用する場合に実行する必要があります。

しかし、パフォーマンスを向上させるために、代償を払いました。このアプローチは、以前とはまったく異なるデザインパターンに従いました。 このサイトの以前のアプローチは、(私が正しく思い出せば)受信と送信をブロックする1つのモノリシックプロセスでした。 私はイベント駆動型ソリューションを開発していたので、この新しいモデルに合うように残りのコードもリファクタリングする必要がありました。

具体的には、新しいアプローチでは、次のように受信と送信を処理するメインループがありました。

Pythonビデオストリーミングソリューションは、RTMP、FLV「タグ」およびマルチキャストビデオストリーミングの組み合わせを使用しました。

  1. 受信したデータは(メッセージとして)RTMPレイヤーに渡されました。
  2. RTMPを分析し、FLVタグを抽出しました。
  3. FLVデータは、バッファリングおよびマルチキャストレイヤーに送信されました。このレイヤーは、ストリームを編成し、送信者の低レベルのバッファーを埋めました。
  4. 送信者は、最後に送信されたインデックスを使用してすべてのクライアントの構造体を保持し、クライアントにできるだけ多くのデータを送信しようとしました。

これはデータのローリングウィンドウであり、クライアントが遅すぎて受信できない場合にフレームをドロップするためのヒューリスティックが含まれていました。 物事はかなりうまくいきました。

システムレベル、アーキテクチャ、およびハードウェアの問題

しかし、別の問題が発生しました。カーネルのコンテキストスイッチが負担になりつつありました。 その結果、瞬時ではなく、100ミリ秒ごとにのみ書き込むことを選択しました。 これにより、小さいパケットが集約され、コンテキストスイッチのバーストが防止されました。

おそらく、サーバーアーキテクチャの領域には、より大きな問題があります。負荷分散とフェイルオーバーに対応したクラスターが必要でした。サーバーの誤動作が原因でユーザーを失うのは楽しいことではありません。 最初は、指定された「ディレクター」が需要を予測することによって放送局のフィードを作成および破棄しようとする、別のディレクターのアプローチを採用しました。 これは見事に失敗しました。 実際、私たちが試したすべてはかなり実質的に失敗しました。 最終的に、クラスターのノード間でブロードキャスターをランダムに共有し、トラフィックを均等化するという比較的強引なアプローチを選択しました。

これは機能しましたが、1つの欠点がありました。一般的なケースはかなりうまく処理されましたが、サイトの全員(または不釣り合いな数のユーザー)が1つの放送局を視聴したときにひどいパフォーマンスが見られました。 良いニュース:これはマーケティングキャンペーンの外では決して起こりません。 このシナリオを処理するために別のクラスターを実装しましたが、実際には、マーケティング活動のために有料ユーザーエクスペリエンスを危険にさらすことは無意味であると推論しました。実際、これは実際には本物のシナリオではありませんでした。場合)。

結論

最終結果からのいくつかの統計:クラスターの1日のトラフィックは、ピーク時(60%の負荷)で約10万人のユーザーであり、平均で約5万人でした。 私は2つのクラスター(HUNとUS)を管理しました。 それぞれが負荷を分担するために約40台のマシンを処理しました。 クラスターの合計帯域幅は約50Gbpsであり、ピーク負荷時に約10Gbpsを使用していました。 結局、私は10Gbps/マシンを簡単に押し出すことができました。 理論的には1であり、この数は1マシンあたり30 Gbpsに達する可能性があります。これは、1台のサーバーから同時にストリームを視聴している約30万人のユーザーに相当します。

既存のFMSクラスターには200台を超えるマシンが含まれていましたが、これを15台に置き換えることができました。実際の作業を行うのはそのうちの10台だけでした。 これにより、およそ200/10=20倍の改善が得られました。

おそらく、Pythonビデオストリーミングプロジェクトからの私の最大のポイントは、新しいスキルセットを学ぶ必要があるという見通しにとらわれてはいけないということでした。 特に、Python、トランスコーディング、およびオブジェクト指向プログラミングはすべて、このマルチキャストビデオプロジェクトに取り組む前に私が非常に専門的でない経験をした概念でした。

それと、独自のソリューションをローリングすることで大きな利益を得ることができます。

1後で、コードを本番環境に導入したときに、PCI帯域幅が低いために10ギガビットイーサネットカードを処理できなかった古いsr2500 Intelサーバーを使用したため、ハードウェアの問題が発生しました。 代わりに、1〜4x1ギガビットイーサネットボンドでそれらを使用しました(複数のネットワークインターフェイスカードのパフォーマンスを仮想カードに集約します)。 最終的に、新しいsr2600 i7 Intelのいくつかを入手しました。これは、パフォーマンスの問題なしに10Gbpsのオプティクスを提供しました。 予測されるすべての計算は、このハードウェアを参照しています。