Bagaimana Saya Membuat Porno 20x Lebih Efisien dengan Streaming Video Python
Diterbitkan: 2022-03-11pengantar
Porno adalah industri besar. Tidak banyak situs di Internet yang dapat menyaingi lalu lintas pemain terbesarnya.
Dan menyulap lalu lintas yang sangat besar ini sangat sulit. Untuk membuat segalanya lebih sulit, banyak konten yang disajikan dari situs porno terdiri dari streaming video langsung dengan latensi rendah daripada konten video statis sederhana. Tetapi untuk semua tantangan yang terlibat, jarang saya membaca tentang pengembang python yang mengambilnya. Jadi saya memutuskan untuk menulis tentang pengalaman saya sendiri di tempat kerja.
Apa masalahnya?
Beberapa tahun yang lalu, saya bekerja untuk situs web ke-26 (saat itu) yang paling banyak dikunjungi di dunia—bukan hanya industri porno: dunia.
Pada saat itu, situs tersebut melayani permintaan streaming video porno dengan protokol Real Time Messaging (RTMP). Lebih khusus lagi, ini menggunakan solusi Flash Media Server (FMS), yang dibuat oleh Adobe, untuk menyediakan streaming langsung kepada pengguna. Proses dasarnya adalah sebagai berikut:
- Pengguna meminta akses ke beberapa streaming langsung
- Server membalas dengan sesi RTMP yang memutar rekaman yang diinginkan
Untuk beberapa alasan, FMS bukanlah pilihan yang baik bagi kami, dimulai dengan biayanya, termasuk pembelian keduanya:
- Lisensi Windows untuk setiap mesin tempat kami menjalankan FMS.
- ~$4k lisensi khusus FMS, yang harus kami beli beberapa ratus (dan lebih banyak lagi setiap hari) karena skala kami.
Semua biaya ini mulai menumpuk. Dan selain biaya, FMS adalah produk yang kurang, terutama dalam fungsinya (lebih lanjut tentang ini sedikit). Jadi saya memutuskan untuk menghapus FMS dan menulis parser Python RTMP saya sendiri dari awal.
Pada akhirnya, saya berhasil membuat layanan kami kira-kira 20x lebih efisien.
Mulai
Ada dua masalah inti yang terlibat: pertama, RTMP dan protokol dan format Adobe lainnya tidak terbuka (yaitu, tersedia untuk umum), yang membuat mereka sulit untuk digunakan. Bagaimana Anda bisa membalikkan atau mengurai file dalam format yang Anda tidak tahu apa-apa? Untungnya, ada beberapa upaya pembalikan yang tersedia di ruang publik (tidak diproduksi oleh Adobe, melainkan oleh grup bernama OS Flash, yang sekarang sudah tidak berfungsi) yang menjadi dasar pekerjaan kami.
Catatan: Adobe kemudian merilis "spesifikasi" yang berisi informasi tidak lebih dari apa yang telah diungkapkan di wiki dan dokumen pembalik yang tidak diproduksi oleh Adobe. Spesifikasi mereka (Adobe) memiliki kualitas yang sangat rendah dan membuatnya hampir tidak mungkin untuk benar-benar menggunakan perpustakaan mereka. Selain itu, protokol itu sendiri kadang-kadang tampak sengaja menyesatkan. Sebagai contoh:
- Mereka menggunakan bilangan bulat 29-bit.
- Mereka menyertakan header protokol dengan pemformatan big endian di mana-mana—kecuali untuk bidang tertentu (belum bertanda), yang merupakan little endian.
- Mereka memeras data ke dalam ruang yang lebih kecil dengan mengorbankan daya komputasi saat mengangkut bingkai video 9k, yang tidak masuk akal, karena mereka mendapatkan kembali bit atau byte pada suatu waktu—keuntungan yang tidak signifikan untuk ukuran file seperti itu.
Dan kedua: RTMP sangat berorientasi pada sesi, yang membuatnya hampir tidak mungkin untuk melakukan multicast aliran masuk. Idealnya, jika beberapa pengguna ingin menonton streaming langsung yang sama, kami dapat memberikan mereka kembali petunjuk ke satu sesi di mana streaming tersebut ditayangkan (ini akan menjadi streaming video multicast). Tetapi dengan RTMP, kami harus membuat instance aliran yang sama sekali baru untuk setiap pengguna yang menginginkan akses. Ini benar-benar sia-sia.
Solusi Streaming Video Multicast Saya
Dengan mengingat hal itu, saya memutuskan untuk mengemas ulang/mengurai aliran respons khas ke dalam 'tag' FLV (di mana 'tag' hanyalah beberapa video, audio, atau data meta). Tag FLV ini dapat berjalan di dalam RTMP dengan sedikit masalah.
Manfaat dari pendekatan seperti itu:
- Kami hanya perlu mengemas ulang aliran satu kali (pengemasan ulang adalah mimpi buruk karena kurangnya spesifikasi dan kebiasaan protokol yang diuraikan di atas).
- Kami dapat menggunakan kembali aliran apa pun di antara klien dengan sedikit masalah dengan menyediakan mereka hanya dengan header FLV, sementara pointer internal ke tag FLV (bersama dengan semacam offset untuk menunjukkan di mana mereka berada di aliran) memungkinkan akses ke konten.
Saya memulai pengembangan dalam bahasa yang paling saya ketahui saat itu: C. Seiring waktu, pilihan ini menjadi rumit; jadi saya mulai mempelajari dasar-dasar Python sambil mem-porting kode C saya. Proses pengembangan dipercepat, tetapi setelah beberapa demo, saya dengan cepat mengalami masalah sumber daya yang melelahkan. Penanganan soket Python tidak dimaksudkan untuk menangani jenis situasi ini: khususnya, dalam Python kami mendapati diri kami membuat beberapa panggilan sistem dan sakelar konteks per tindakan, menambahkan sejumlah besar overhead.
Meningkatkan Performa Streaming Video: Mencampur Python, RTMP, dan C
Setelah membuat profil kode, saya memilih untuk memindahkan fungsi-fungsi kritis kinerja ke dalam modul Python yang ditulis seluruhnya dalam C. Ini adalah hal-hal tingkat rendah: khususnya, itu memanfaatkan mekanisme epoll kernel untuk menyediakan urutan pertumbuhan logaritmik .
Dalam pemrograman soket asinkron ada fasilitas yang dapat memberi Anda info apakah soket yang diberikan dapat dibaca/ditulis/diisi kesalahan. Di masa lalu, pengembang telah menggunakan panggilan sistem select() untuk mendapatkan informasi ini, yang skalanya buruk. Poll() adalah versi pilih yang lebih baik, tetapi masih tidak terlalu bagus karena Anda harus memasukkan banyak deskriptor soket di setiap panggilan.

Epoll luar biasa karena yang harus Anda lakukan hanyalah mendaftarkan soket dan sistem akan mengingat soket yang berbeda itu, menangani semua detail kasar secara internal. Jadi tidak ada overhead yang lewat argumen dengan setiap panggilan. Ini juga menskalakan jauh lebih baik dan hanya mengembalikan soket yang Anda pedulikan, yang jauh lebih baik daripada menjalankan daftar deskriptor soket 100k untuk melihat apakah mereka memiliki acara dengan bitmask--yang perlu Anda lakukan jika Anda menggunakan solusi lain.
Tetapi untuk peningkatan kinerja, kami membayar harga: pendekatan ini mengikuti pola desain yang sama sekali berbeda dari sebelumnya. Pendekatan situs sebelumnya adalah (jika saya ingat dengan benar) satu proses monolitik yang memblokir penerimaan dan pengiriman; Saya sedang mengembangkan solusi yang digerakkan oleh peristiwa, jadi saya harus memfaktorkan ulang sisa kode juga agar sesuai dengan model baru ini.
Secara khusus, dalam pendekatan baru kami, kami memiliki loop utama, yang menangani penerimaan dan pengiriman sebagai berikut:
- Data yang diterima diteruskan (sebagai pesan) hingga ke lapisan RTMP.
- RTMP dibedah dan tag FLV diekstraksi.
- Data FLV dikirim ke lapisan buffering dan multicasting, yang mengatur aliran dan mengisi buffer tingkat rendah pengirim.
- Pengirim menyimpan struct untuk setiap klien, dengan indeks yang dikirim terakhir, dan mencoba mengirim data sebanyak mungkin ke klien.
Ini adalah jendela data yang bergulir, dan menyertakan beberapa heuristik untuk menjatuhkan bingkai saat klien terlalu lambat untuk menerima. Hal-hal bekerja dengan cukup baik.
Masalah tingkat sistem, arsitektur, dan perangkat keras
Tetapi kami mengalami masalah lain: sakelar konteks kernel menjadi beban. Akibatnya, kami memilih untuk menulis hanya setiap 100 milidetik, bukan secara instan. Ini mengumpulkan paket-paket yang lebih kecil dan mencegah ledakan sakelar konteks.
Mungkin masalah yang lebih besar terletak di ranah arsitektur server: kami membutuhkan klaster yang mampu menyeimbangkan beban dan failover—kehilangan pengguna karena kegagalan fungsi server bukanlah hal yang menyenangkan. Pada awalnya, kami menggunakan pendekatan sutradara terpisah, di mana 'sutradara' yang ditunjuk akan mencoba membuat dan menghancurkan feed penyiar dengan memprediksi permintaan. Ini gagal secara spektakuler. Faktanya, semua yang kami coba gagal secara substansial. Pada akhirnya, kami memilih pendekatan yang relatif kasar untuk berbagi penyiar di antara node cluster secara acak, menyamai lalu lintas.
Ini berhasil, tetapi dengan satu kelemahan: meskipun kasus umum ditangani dengan cukup baik, kami melihat kinerja yang buruk ketika semua orang di situs (atau jumlah pengguna yang tidak proporsional) menonton satu penyiar. Kabar baiknya: ini tidak pernah terjadi di luar kampanye pemasaran. Kami menerapkan cluster terpisah untuk menangani skenario ini, tetapi sebenarnya kami beralasan bahwa membahayakan pengalaman pengguna yang membayar untuk upaya pemasaran tidak masuk akal—sebenarnya, ini bukan skenario asli (walaupun akan menyenangkan untuk menangani setiap skenario yang bisa dibayangkan. kasus).
Kesimpulan
Beberapa statistik dari hasil akhir: Lalu lintas harian di kluster sekitar 100 ribu pengguna pada puncaknya (60% beban), ~50 ribu rata-rata. Saya mengelola dua cluster (HUN dan US); masing-masing menangani sekitar 40 mesin untuk berbagi beban. Bandwidth agregat cluster adalah sekitar 50 Gbps, dari mana mereka menggunakan sekitar 10 Gbps saat beban puncak. Pada akhirnya, saya berhasil mendorong 10 Gbps/mesin dengan mudah; secara teoritis 1 , jumlah ini bisa mencapai 30 Gbps/mesin, yang berarti sekitar 300 ribu pengguna menonton streaming secara bersamaan dari satu server.
Cluster FMS yang ada berisi lebih dari 200 mesin, yang dapat digantikan oleh 15 mesin saya—hanya 10 yang dapat melakukan pekerjaan nyata. Ini memberi kami kira-kira peningkatan 200/10 = 20x.
Mungkin take-away terbesar saya dari proyek streaming video Python adalah bahwa saya tidak boleh membiarkan diri saya dihentikan oleh prospek harus mempelajari keahlian baru. Secara khusus, Python, transcoding, dan pemrograman berorientasi objek, semuanya adalah konsep yang saya miliki dengan pengalaman yang sangat sub-profesional sebelum mengambil proyek video multicast ini.
Itu, dan menggulirkan solusi Anda sendiri dapat menghasilkan banyak uang.
1 Kemudian, ketika kami memasukkan kode ke dalam produksi, kami mengalami masalah perangkat keras, karena kami menggunakan server Intel sr2500 lama yang tidak dapat menangani kartu Ethernet 10 Gbit karena bandwidth PCI yang rendah. Sebagai gantinya, kami menggunakannya dalam ikatan Ethernet 1-4x1 Gbit (menggabungkan kinerja beberapa kartu antarmuka jaringan ke dalam kartu virtual). Akhirnya, kami mendapatkan beberapa Intel sr2600 i7 yang lebih baru, yang melayani 10 Gbps melalui optik tanpa gangguan kinerja. Semua perhitungan yang diproyeksikan mengacu pada perangkat keras ini.