ฉันสร้างหนังโป๊ให้มีประสิทธิภาพมากขึ้น 20 เท่าด้วย Python Video Streaming
เผยแพร่แล้ว: 2022-03-11บทนำ
หนังโป๊เป็นอุตสาหกรรมขนาดใหญ่ มีเว็บไซต์ไม่มากนักบนอินเทอร์เน็ตที่สามารถแข่งขันกับปริมาณการใช้งานของผู้เล่นรายใหญ่ที่สุดได้
และการเล่นกลกับการจราจรที่หนาแน่นนี้เป็นเรื่องยาก เพื่อทำให้สิ่งต่าง ๆ ยากขึ้น เนื้อหาส่วนใหญ่ที่ให้บริการจากเว็บไซต์ลามกนั้นประกอบด้วยการสตรีมวิดีโอสดที่มีเวลาแฝงต่ำ แทนที่จะเป็นเนื้อหาวิดีโอคงที่ทั่วไป แต่สำหรับความท้าทายทั้งหมดที่เกี่ยวข้อง ฉันไม่ค่อยได้อ่านเกี่ยวกับนักพัฒนาหลามที่รับมือมัน ดังนั้นฉันจึงตัดสินใจเขียนเกี่ยวกับประสบการณ์ของตัวเองในงาน
มีปัญหาอะไร?
เมื่อสองสามปีก่อน ฉันทำงานให้กับเว็บไซต์ที่มีผู้เข้าชมมากที่สุดในโลกเป็นครั้งที่ 26 (ในขณะนั้น) ไม่ใช่แค่อุตสาหกรรมลามกเท่านั้น: โลก
ในขณะนั้น ไซต์ดังกล่าวได้ส่งคำขอสตรีมวิดีโอโป๊ด้วยโปรโตคอลการส่งข้อความแบบเรียลไทม์ (RTMP) โดยเฉพาะอย่างยิ่ง ใช้โซลูชัน Flash Media Server (FMS) ซึ่งสร้างโดย Adobe เพื่อให้สตรีมแบบสดแก่ผู้ใช้ กระบวนการพื้นฐานมีดังนี้:
- ผู้ใช้ร้องขอการเข้าถึงสตรีมสดบางรายการ
- เซิร์ฟเวอร์ตอบกลับด้วยเซสชัน RTMP ที่เล่นฟุตเทจที่ต้องการ
ด้วยเหตุผลสองสามประการ FMS ไม่ใช่ทางเลือกที่ดีสำหรับเรา เริ่มต้นด้วยต้นทุน ซึ่งรวมถึงการซื้อทั้งสองอย่าง:
- ใบอนุญาต Windows สำหรับทุกเครื่องที่เราใช้งาน FMS
- ใบอนุญาตเฉพาะ FMS ประมาณ $4k ซึ่งเราต้องซื้อหลายร้อย (และมากกว่านั้นทุกวัน) เนื่องจากขนาดของเรา
ค่าธรรมเนียมทั้งหมดเหล่านี้เริ่มเพิ่มขึ้น และนอกจากเรื่องค่าใช้จ่ายแล้ว FMS ยังเป็นผลิตภัณฑ์ที่ขาดตลาด โดยเฉพาะอย่างยิ่งในด้านฟังก์ชันการทำงาน (เพิ่มเติมในเรื่องนี้เล็กน้อย) ดังนั้นฉันจึงตัดสินใจทิ้ง FMS และเขียน Python RTMP parser ของตัวเองตั้งแต่ต้น
ในที่สุด ฉันก็สามารถทำให้บริการของเรามีประสิทธิภาพมากขึ้นประมาณ 20 เท่า
เริ่มต้น
มีปัญหาหลักสองประการที่เกี่ยวข้อง: ประการแรก RTMP และโปรโตคอลและรูปแบบอื่นๆ ของ Adobe ไม่ได้เปิดอยู่ (เช่น มีให้ในที่สาธารณะ) ซึ่งทำให้ยากต่อการทำงานด้วย คุณจะย้อนกลับหรือแยกวิเคราะห์ไฟล์ในรูปแบบที่คุณไม่รู้อะไรเลยได้อย่างไร โชคดีที่มีความพยายามในการย้อนกลับในพื้นที่สาธารณะ (ไม่ได้ผลิตโดย Adobe แต่เกิดจากกลุ่มที่เรียกว่า OS Flash ซึ่งปัจจุบันเลิกใช้แล้ว) ซึ่งเราใช้งานของเราเป็นหลัก
หมายเหตุ: Adobe ได้เผยแพร่ "ข้อกำหนด" ในภายหลังซึ่งไม่มีข้อมูลมากไปกว่าที่เปิดเผยแล้วในวิกิและเอกสารย้อนกลับที่ไม่ได้ผลิตโดย Adobe ข้อมูลจำเพาะ (ของ Adobe) มีคุณภาพต่ำอย่างไร้เหตุผล และทำให้แทบจะเป็นไปไม่ได้เลยที่จะใช้ไลบรารีของพวกเขาจริงๆ นอกจากนี้ โปรโตคอลเองก็ดูเหมือนตั้งใจทำให้เข้าใจผิดในบางครั้ง ตัวอย่างเช่น:
- พวกเขาใช้จำนวนเต็ม 29 บิต
- รวมส่วนหัวของโปรโตคอลที่มีการจัดรูปแบบ endian ขนาดใหญ่ทุกที่ ยกเว้นฟิลด์เฉพาะ (แต่ยังไม่ได้ทำเครื่องหมาย) ซึ่งเป็น endian เพียงเล็กน้อย
- พวกเขาบีบอัดข้อมูลลงในพื้นที่น้อยลงโดยใช้กำลังประมวลผลเมื่อขนส่งเฟรมวิดีโอ 9k ซึ่งไม่สมเหตุสมผลเลย เพราะพวกเขาได้รับบิตหรือไบต์กลับในแต่ละครั้ง เพิ่มขึ้นเล็กน้อยสำหรับขนาดไฟล์ดังกล่าว
และประการที่สอง: RTMP เป็นแบบเน้นเซสชันสูง ซึ่งทำให้แทบเป็นไปไม่ได้เลยที่จะมัลติคาสต์สตรีมขาเข้า ตามหลักการแล้ว หากผู้ใช้หลายคนต้องการดูสตรีมแบบสดเดียวกัน เราก็สามารถส่งพวกเขากลับไปยังเซสชันเดียวที่มีการออกอากาศสตรีมนั้นได้ (ซึ่งจะเป็นการสตรีมวิดีโอแบบหลายผู้รับ) แต่ด้วย RTMP เราต้องสร้างสตรีมใหม่ทั้งหมดสำหรับผู้ใช้ทุกคนที่ต้องการเข้าถึง นี่เป็นของเสียโดยสิ้นเชิง
โซลูชันการสตรีมวิดีโอ Multicast ของฉัน
ด้วยเหตุนี้ ฉันจึงตัดสินใจจัดแพคเกจใหม่/แยกวิเคราะห์สตรีมการตอบกลับทั่วไปลงใน 'แท็ก' ของ FLV (โดยที่ 'แท็ก' เป็นเพียงวิดีโอ เสียง หรือข้อมูลเมตาบางส่วน) แท็ก FLV เหล่านี้สามารถเดินทางภายใน RTMP โดยไม่มีปัญหาเล็กน้อย
ประโยชน์ของวิธีการดังกล่าว:
- เราจำเป็นต้องรีแพ็คเกจสตรีมเพียงครั้งเดียว (การรีแพ็คเกจเป็นฝันร้ายเนื่องจากขาดคุณสมบัติและโปรโตคอลที่ไม่ชอบมาพากลที่กล่าวไว้ข้างต้น)
- เราสามารถใช้สตรีมใหม่ระหว่างไคลเอ็นต์ที่มีปัญหาน้อยมากได้โดยใช้ส่วนหัว FLV ในขณะที่ตัวชี้ภายในไปยังแท็ก FLV (พร้อมกับออฟเซ็ตบางประเภทเพื่อระบุว่าพวกเขาอยู่ที่ใดในสตรีม) อนุญาตให้เข้าถึงได้ ในเนื้อหา.
ฉันเริ่มพัฒนาในภาษาที่ฉันรู้จักดีที่สุดในขณะนั้น C. เมื่อเวลาผ่านไป ทางเลือกนี้ก็ยุ่งยาก ดังนั้นฉันจึงเริ่มเรียนรู้พื้นฐานของ Python ขณะย้ายโค้ด C ของฉัน กระบวนการพัฒนาเร็วขึ้น แต่หลังจากการสาธิตไม่กี่ครั้ง ฉันก็พบปัญหาในการใช้ทรัพยากรจนหมดอย่างรวดเร็ว การจัดการซ็อกเก็ตของ Python ไม่ได้มีไว้เพื่อจัดการกับสถานการณ์ประเภทนี้: โดยเฉพาะใน Python เราพบว่าเราทำการเรียกระบบหลายครั้งและการสลับบริบทต่อการกระทำแต่ละครั้ง ทำให้เกิดโอเวอร์เฮดจำนวนมาก
การปรับปรุงประสิทธิภาพการสตรีมวิดีโอ: การผสม Python, RTMP และ C
หลังจากสร้างโปรไฟล์โค้ดแล้ว ฉันเลือกที่จะย้ายฟังก์ชันที่มีความสำคัญต่อประสิทธิภาพไปยังโมดูล Python ที่เขียนด้วยภาษา C ทั้งหมด นี่เป็นเนื้อหาระดับต่ำ โดยเฉพาะอย่างยิ่ง มันใช้ประโยชน์จากกลไก epoll ของเคอร์เนลเพื่อจัดลำดับการเติบโตแบบลอการิทึม .
ในการเขียนโปรแกรมซ็อกเก็ตแบบอะซิงโครนัสมีสิ่งอำนวยความสะดวกที่สามารถให้ข้อมูลแก่คุณว่าซ็อกเก็ตที่ระบุนั้นสามารถอ่าน/เขียนได้/มีข้อผิดพลาดหรือไม่ ในอดีต นักพัฒนาซอฟต์แวร์ใช้การเรียกระบบ select() เพื่อรับข้อมูลนี้ ซึ่งปรับขนาดได้ไม่ดี Poll() เป็นเวอร์ชันการเลือกที่ดีกว่า แต่ก็ยังไม่ได้ยอดเยี่ยมเท่าที่คุณต้องส่งผ่านตัวบอกซ็อกเก็ตจำนวนมากทุกครั้งที่โทร

Epoll นั้นยอดเยี่ยมมาก เพราะสิ่งที่คุณต้องทำคือลงทะเบียนซ็อกเก็ต และระบบจะจดจำซ็อกเก็ตที่แตกต่างกันนั้น ซึ่งจัดการรายละเอียดที่สำคัญทั้งหมดภายใน ดังนั้นจึงไม่มีค่าใช้จ่ายในการโต้แย้งในการโทรแต่ละครั้ง นอกจากนี้ยังปรับขนาดได้ดีกว่ามากและส่งคืนเฉพาะซ็อกเก็ตที่คุณสนใจ ซึ่งดีกว่าการเรียกใช้รายการตัวอธิบายซ็อกเก็ต 100k เพื่อดูว่ามีเหตุการณ์ที่มีบิตมาสก์หรือไม่ ซึ่งคุณต้องดำเนินการหากคุณใช้วิธีแก้ไขปัญหาอื่น
แต่สำหรับการเพิ่มประสิทธิภาพ เราจ่ายราคา: แนวทางนี้ใช้รูปแบบการออกแบบที่ต่างไปจากเดิมอย่างสิ้นเชิง วิธีการก่อนหน้านี้ของไซต์คือ (ถ้าฉันจำได้ถูกต้อง) กระบวนการเสาหินหนึ่งกระบวนการซึ่งขัดขวางการรับและส่ง ฉันกำลังพัฒนาโซลูชันที่ขับเคลื่อนด้วยเหตุการณ์ ดังนั้นฉันจึงต้องปรับโครงสร้างโค้ดที่เหลือใหม่เพื่อให้เข้ากับโมเดลใหม่นี้
โดยเฉพาะอย่างยิ่ง ในแนวทางใหม่ของเรา เรามีลูปหลัก ซึ่งจัดการการรับและส่งดังนี้:
- ข้อมูลที่ได้รับถูกส่งผ่าน (เป็นข้อความ) ไปยังเลเยอร์ RTMP
- RTMP ถูกผ่าและแยกแท็ก FLV
- ข้อมูล FLV ถูกส่งไปยังชั้นบัฟเฟอร์และมัลติคาสติ้ง ซึ่งจัดระเบียบสตรีมและเติมบัฟเฟอร์ระดับต่ำของผู้ส่ง
- ผู้ส่งเก็บโครงสร้างสำหรับลูกค้าทุกรายด้วยดัชนีที่ส่งล่าสุด และพยายามส่งข้อมูลไปยังไคลเอ็นต์ให้ได้มากที่สุด
นี่เป็นหน้าต่างข้อมูลแบบหมุนเวียน และรวมฮิวริสติกบางตัวเพื่อวางเฟรมเมื่อไคลเอ็นต์รับช้าเกินไป สิ่งต่าง ๆ ทำงานได้ดี
ปัญหาระดับระบบ สถาปัตยกรรม และฮาร์ดแวร์
แต่เราพบปัญหาอื่น: การสลับบริบทของเคอร์เนลกลายเป็นภาระ ด้วยเหตุนี้ เราจึงเลือกที่จะเขียนทุกๆ 100 มิลลิวินาทีเท่านั้น แทนที่จะเขียนในทันที ซึ่งรวมแพ็กเก็ตที่มีขนาดเล็กลงและป้องกันการสลับบริบทจำนวนมาก
บางทีปัญหาที่ใหญ่กว่าอาจอยู่ในขอบเขตของสถาปัตยกรรมเซิร์ฟเวอร์: เราต้องการคลัสเตอร์ที่จัดสรรภาระงานและเฟลโอเวอร์ได้ การสูญเสียผู้ใช้เนื่องจากเซิร์ฟเวอร์ทำงานผิดพลาดไม่ใช่เรื่องสนุก ในตอนแรก เราใช้แนวทางแยกผู้อำนวยการ ซึ่ง 'ผู้กำกับ' ที่ได้รับมอบหมายจะพยายามสร้างและทำลายฟีดผู้ออกอากาศโดยคาดการณ์ความต้องการ สิ่งนี้ล้มเหลวอย่างน่าทึ่ง อันที่จริงทุกสิ่งที่เราพยายามล้มเหลวอย่างมาก ในท้ายที่สุด เราเลือกใช้วิธีการที่ค่อนข้างดุร้ายในการแบ่งปันผู้แพร่ภาพกระจายเสียงระหว่างโหนดของคลัสเตอร์แบบสุ่ม เท่ากับปริมาณการรับส่งข้อมูล
วิธีนี้ใช้ได้ผล แต่มีข้อเสียอยู่อย่างหนึ่ง: แม้ว่ากรณีทั่วไปจะได้รับการจัดการได้ค่อนข้างดี แต่เราเห็นประสิทธิภาพที่แย่มากเมื่อทุกคนในไซต์ (หรือผู้ใช้จำนวนไม่สมส่วน) ดูผู้แพร่ภาพกระจายเสียงเพียงคนเดียว ข่าวดี: สิ่งนี้ไม่เคยเกิดขึ้นนอกแคมเปญการตลาด เราใช้คลัสเตอร์แยกต่างหากเพื่อจัดการกับสถานการณ์นี้ แต่ในความเป็นจริง เราให้เหตุผลว่าการเสี่ยงต่อประสบการณ์ของผู้ใช้ที่จ่ายเงินสำหรับความพยายามทางการตลาดนั้นไร้เหตุผล อันที่จริง นี่ไม่ใช่สถานการณ์จริง (แม้ว่าจะดีที่จะจัดการกับทุกจินตนาการก็ตาม) กรณี).
บทสรุป
สถิติบางส่วนจากผลลัพธ์สุดท้าย: การรับส่งข้อมูลรายวันบนคลัสเตอร์มีผู้ใช้ประมาณ 100,000 รายที่จุดสูงสุด (โหลด 60%) โดยเฉลี่ยประมาณ 50k ฉันจัดการสองคลัสเตอร์ (HUN และ US); แต่ละคนใช้เครื่องจักรประมาณ 40 เครื่องเพื่อแบ่งเบาภาระ แบนด์วิดท์รวมของคลัสเตอร์อยู่ที่ประมาณ 50 Gbps ซึ่งใช้ประมาณ 10 Gbps ในขณะที่โหลดสูงสุด ในท้ายที่สุด ฉันสามารถผลักดัน 10 Gbps/เครื่องได้อย่างง่ายดาย ในทางทฤษฎี 1 จำนวนนี้อาจสูงถึง 30 Gbps/เครื่อง ซึ่งแปลเป็นผู้ใช้ประมาณ 300,000 คนที่ดูสตรีมพร้อมกันจากเซิร์ฟเวอร์เดียว
คลัสเตอร์ FMS ที่มีอยู่มีเครื่องมากกว่า 200 เครื่อง ซึ่งสามารถแทนที่ด้วย 15 เครื่องของฉัน ซึ่งมีเพียง 10 เครื่องเท่านั้นที่ใช้งานได้จริง สิ่งนี้ทำให้เรามีการปรับปรุงประมาณ 200/10 = 20 เท่า
อาจเป็นไปได้ว่าโครงการสตรีมมิ่งวิดีโอ Python ที่ยิ่งใหญ่ที่สุดของฉันคือการที่ฉันไม่ควรปล่อยให้ตัวเองถูกหยุดโดยโอกาสที่จะต้องเรียนรู้ชุดทักษะใหม่ โดยเฉพาะอย่างยิ่ง Python การทรานส์โค้ด และการเขียนโปรแกรมเชิงวัตถุ ล้วนเป็นแนวคิดที่ฉันมีประสบการณ์ระดับมืออาชีพย่อยมากก่อนที่จะทำโปรเจ็กต์วิดีโอมัลติคาสต์นี้
นั้นและนั่นก็ทำให้โซลูชันของคุณเองสามารถจ่ายได้มาก
1 ต่อมา เมื่อเรานำโค้ดไปใช้ในการผลิต เราพบปัญหาเกี่ยวกับฮาร์ดแวร์ เนื่องจากเราใช้เซิร์ฟเวอร์ Intel sr2500 รุ่นเก่ากว่าซึ่งไม่สามารถจัดการการ์ด 10 Gbit Ethernet ได้เนื่องจากแบนด์วิดท์ PCI ต่ำ แต่เราใช้พวกมันในพันธะอีเทอร์เน็ต 1-4x1 Gbit (รวมประสิทธิภาพของการ์ดอินเทอร์เฟซเครือข่ายหลายตัวเป็นการ์ดเสมือน) ในที่สุด เราก็ได้ sr2600 i7 Intels ที่ใหม่กว่าซึ่งให้บริการ 10 Gbps ผ่านออปติกโดยไม่มีข้อบกพร่องด้านประสิทธิภาพ การคำนวณที่คาดการณ์ไว้ทั้งหมดอ้างอิงถึงฮาร์ดแวร์นี้