RoadRunner: PHP ไม่ได้สร้างมาเพื่อตาย หรือ Golang มาเพื่อช่วยเหลือ

RoadRunner: PHP ไม่ได้สร้างมาเพื่อตาย หรือ Golang มาเพื่อช่วยเหลือ

สวัสดีฮับ! เรากำลังใช้งาน Badoo ทำงานกับประสิทธิภาพของ PHPเนื่องจากเรามีระบบที่ค่อนข้างใหญ่ในภาษานี้และปัญหาด้านประสิทธิภาพเป็นเรื่องของการประหยัดเงิน กว่าสิบปีที่แล้ว เราได้สร้าง PHP-FPM สำหรับสิ่งนี้ ซึ่งในตอนแรกเป็นชุดแพตช์สำหรับ PHP และต่อมาก็กลายเป็นส่วนหนึ่งของการเผยแพร่อย่างเป็นทางการ

ในช่วงไม่กี่ปีที่ผ่านมา PHP มีความก้าวหน้าอย่างมาก: ตัวรวบรวมขยะได้รับการปรับปรุง ระดับความเสถียรเพิ่มขึ้น - วันนี้คุณสามารถเขียน daemons และสคริปต์ที่มีอายุยืนยาวใน PHP ได้โดยไม่มีปัญหาใด ๆ สิ่งนี้ทำให้ Spiral Scout ดำเนินต่อไปได้: RoadRunner ซึ่งแตกต่างจาก PHP-FPM ตรงที่ไม่ได้ล้างหน่วยความจำระหว่างคำขอ ซึ่งให้ประโยชน์ด้านประสิทธิภาพเพิ่มเติม (แม้ว่าแนวทางนี้จะทำให้กระบวนการพัฒนาซับซ้อนก็ตาม) ขณะนี้เรากำลังทดลองใช้เครื่องมือนี้ แต่เรายังไม่มีผลลัพธ์ใดๆ ที่จะแบ่งปัน เพื่อให้การรอคอยเป็นเรื่องสนุกยิ่งขึ้น เรากำลังเผยแพร่คำแปลของประกาศ RoadRunner จาก Spiral Scout

แนวทางจากบทความนี้อยู่ใกล้กับเรา: เมื่อแก้ไขปัญหาเรามักใช้ PHP และ Go ร่วมกันโดยได้รับประโยชน์จากทั้งสองภาษาและไม่ยอมแพ้ต่ออีกภาษาหนึ่ง

สนุก!

ตลอดสิบปีที่ผ่านมา เราได้สร้างแอปพลิเคชันสำหรับบริษัทจากรายชื่อนี้ 500 ฟอร์จูนและสำหรับธุรกิจที่มีผู้ชมไม่เกิน 500 ราย ตลอดเวลาที่ผ่านมา วิศวกรของเราได้พัฒนาแบ็กเอนด์โดยใช้ PHP เป็นหลัก แต่เมื่อสองปีที่แล้ว มีบางสิ่งที่ส่งผลกระทบอย่างมากไม่เพียงแต่ต่อประสิทธิภาพของผลิตภัณฑ์ของเราเท่านั้น แต่ยังรวมถึงความสามารถในการขยายขนาดด้วย เราได้แนะนำ Golang (Go) ให้กับกลุ่มเทคโนโลยีของเรา

แทบจะในทันทีที่เราค้นพบว่า Go ช่วยให้เราสร้างแอปพลิเคชันขนาดใหญ่ขึ้นพร้อมประสิทธิภาพที่เร็วขึ้นสูงสุด 40 เท่า ด้วยเหตุนี้ เราจึงสามารถขยายผลิตภัณฑ์ที่มีอยู่ซึ่งเขียนด้วย PHP และปรับปรุงผลิตภัณฑ์โดยรวมข้อดีของทั้งสองภาษาเข้าด้วยกัน

เราจะบอกคุณว่าการรวมกันของ Go และ PHP ช่วยแก้ไขปัญหาการพัฒนาที่แท้จริงได้อย่างไร และวิธีที่มันกลายเป็นเครื่องมือสำหรับเราที่สามารถขจัดปัญหาบางอย่างที่เกี่ยวข้องกับ โมเดล PHP ที่กำลังจะตาย.

สภาพแวดล้อมการพัฒนา PHP ของคุณทุกวัน

ก่อนที่เราจะพูดถึงวิธีที่คุณสามารถใช้ Go เพื่อฟื้นฟูโมเดลที่กำลังจะตายของ PHP มาดูสภาพแวดล้อมการพัฒนา PHP มาตรฐานของคุณกันก่อน

ในกรณีส่วนใหญ่ คุณจะเรียกใช้แอปพลิเคชันโดยใช้เว็บเซิร์ฟเวอร์ nginx และเซิร์ฟเวอร์ PHP-FPM ร่วมกัน รายการแรกให้บริการไฟล์คงที่และเปลี่ยนเส้นทางคำขอเฉพาะไปยัง PHP-FPM และ PHP-FPM เองก็รันโค้ด PHP บางทีคุณอาจใช้ชุดค่าผสมที่ได้รับความนิยมน้อยกว่าจาก Apache และ mod_php แม้ว่าการทำงานจะแตกต่างออกไปเล็กน้อย แต่หลักการก็เหมือนกัน

มาดูกันว่า PHP-FPM รันโค้ดแอปพลิเคชันอย่างไร เมื่อมีการร้องขอมาถึง PHP-FPM จะเริ่มต้นกระบวนการ PHP ลูกและส่งรายละเอียดคำขอโดยเป็นส่วนหนึ่งของสถานะ (_GET, _POST, _SERVER ฯลฯ)

สถานะไม่สามารถเปลี่ยนแปลงได้ในระหว่างการเรียกใช้สคริปต์ PHP ดังนั้นจึงมีเพียงวิธีเดียวเท่านั้นที่จะรับชุดข้อมูลอินพุตใหม่: โดยการล้างหน่วยความจำกระบวนการและเริ่มต้นใหม่

โมเดลการดำเนินการนี้มีข้อดีหลายประการ คุณไม่ต้องกังวลเกี่ยวกับการใช้หน่วยความจำมากนัก กระบวนการทั้งหมดจะถูกแยกออกจากกันโดยสิ้นเชิง และหากกระบวนการใดกระบวนการหนึ่งเสียไป กระบวนการนั้นจะถูกสร้างขึ้นใหม่โดยอัตโนมัติโดยไม่ส่งผลกระทบต่อกระบวนการที่เหลือ แต่วิธีนี้ก็มีข้อเสียเช่นกันที่เกิดขึ้นเมื่อพยายามปรับขนาดแอปพลิเคชัน

ข้อเสียและความไร้ประสิทธิภาพของสภาพแวดล้อม PHP ปกติ

หากคุณมีส่วนร่วมในการพัฒนาอย่างมืออาชีพใน PHP คุณจะรู้ว่าจะเริ่มโปรเจ็กต์ใหม่ได้ที่ไหนโดยการเลือกเฟรมเวิร์ก ประกอบด้วยไลบรารีสำหรับการขึ้นต่อกัน ORM การแปล และเทมเพลต และแน่นอนว่าอินพุตของผู้ใช้ทั้งหมดสามารถรวมไว้ในวัตถุเดียวได้อย่างสะดวก (Symfony/HttpFoundation หรือ PSR-7) เฟรมเวิร์กเจ๋งมาก!

แต่ทุกอย่างมีราคาของมัน ในเฟรมเวิร์กระดับองค์กรใดๆ เพื่อประมวลผลคำขอของผู้ใช้แบบธรรมดาหรือเข้าถึงฐานข้อมูล คุณจะต้องโหลดไฟล์อย่างน้อยหลายสิบไฟล์ สร้างคลาสจำนวนมาก และแยกวิเคราะห์การกำหนดค่าต่างๆ แต่สิ่งที่แย่ที่สุดคือหลังจากเสร็จสิ้นแต่ละงานแล้ว คุณจะต้องรีเซ็ตทุกอย่างและเริ่มต้นใหม่อีกครั้ง: โค้ดทั้งหมดที่คุณเพิ่งเริ่มใช้จะไม่มีประโยชน์ และด้วยความช่วยเหลือนี้ คุณจะไม่ดำเนินการตามคำขออื่นอีกต่อไป บอกสิ่งนี้กับโปรแกรมเมอร์ที่เขียนด้วยภาษาอื่น ๆ แล้วคุณจะเห็นความสับสนบนใบหน้าของเขา

วิศวกร PHP ใช้เวลาหลายปีในการค้นหาวิธีแก้ปัญหานี้ โดยใช้เทคนิค Lazy Loading ที่ชาญฉลาด ไมโครเฟรมเวิร์ก ไลบรารีที่ปรับให้เหมาะสม แคช ฯลฯ แต่สุดท้ายแล้ว คุณยังคงต้องรีเซ็ตแอปพลิเคชันทั้งหมดและเริ่มต้นใหม่ ซ้ำแล้วซ้ำเล่า (หมายเหตุผู้แปล: ปัญหานี้ได้รับการแก้ไขบางส่วนด้วยการถือกำเนิดของ Preload ใน PHP 7.4)

PHP with Go สามารถอยู่รอดได้มากกว่าหนึ่งคำขอหรือไม่

เป็นไปได้ที่จะเขียนสคริปต์ PHP ที่จะใช้เวลานานกว่าสองสามนาที (สูงสุดเป็นชั่วโมงหรือหลายวัน): ตัวอย่างเช่น งาน cron, ตัวแยกวิเคราะห์ CSV, บัสเตอร์คิว ทั้งหมดทำงานตามสถานการณ์เดียวกัน: เรียกงาน ดำเนินการ และรองานถัดไป โค้ดอยู่ในหน่วยความจำ ซึ่งช่วยประหยัดเวลาเป็นมิลลิวินาทีอันมีค่าเนื่องจากต้องมีขั้นตอนเพิ่มเติมมากมายในการโหลดเฟรมเวิร์กและแอปพลิเคชัน

แต่การพัฒนาสคริปต์ที่มีอายุการใช้งานยาวนานนั้นไม่ใช่เรื่องง่าย ข้อผิดพลาดใดๆ จะฆ่ากระบวนการโดยสิ้นเชิง การวินิจฉัยหน่วยความจำรั่วจะทำให้คุณเป็นบ้า และคุณจะไม่สามารถใช้การดีบัก F5 ได้อีกต่อไป

สถานการณ์ได้รับการปรับปรุงด้วยการเปิดตัว PHP 7: มีตัวรวบรวมขยะที่เชื่อถือได้ปรากฏขึ้น ง่ายต่อการจัดการข้อผิดพลาด และส่วนขยายเคอร์เนลได้รับการปกป้องจากการรั่วไหลแล้ว จริงอยู่ที่วิศวกรยังต้องระมัดระวังเรื่องหน่วยความจำและตระหนักถึงปัญหาสถานะในโค้ด (มีภาษาใดบ้างที่เราไม่ต้องกังวลเกี่ยวกับสิ่งเหล่านี้?) แต่ใน PHP 7 ยังมีเรื่องเซอร์ไพรส์น้อยลงรอเราอยู่

เป็นไปได้หรือไม่ที่จะใช้แบบจำลองการทำงานกับสคริปต์ PHP ที่มีอายุยืนยาว มาปรับใช้กับงานเล็กๆ น้อยๆ เช่น การประมวลผลคำขอ HTTP และด้วยเหตุนี้จึงไม่จำเป็นต้องโหลดทุกอย่างตั้งแต่เริ่มต้นสำหรับแต่ละคำขอ

เพื่อแก้ไขปัญหานี้ ก่อนอื่นเราจำเป็นต้องติดตั้งแอปพลิเคชันเซิร์ฟเวอร์ที่สามารถรับคำขอ HTTP และส่งต่อคำขอเหล่านั้นไปยังผู้ปฏิบัติงาน PHP ทีละคนโดยไม่ทำลายมันทุกครั้ง

เรารู้ว่าเราสามารถเขียนเว็บเซิร์ฟเวอร์ด้วย PHP ล้วนๆ (PHP-PM) หรือใช้ส่วนขยาย C (Swoole) และแม้ว่าแต่ละวิธีจะมีข้อดี แต่ทั้งสองตัวเลือกก็ไม่เหมาะกับเรา - เราต้องการอะไรมากกว่านี้ เราต้องการมากกว่าแค่เว็บเซิร์ฟเวอร์ - เราหวังว่าจะได้รับโซลูชันที่สามารถช่วยเราจากปัญหาที่เกี่ยวข้องกับ "การเริ่มต้นยาก" ใน PHP ซึ่งในขณะเดียวกันก็สามารถปรับเปลี่ยนและขยายสำหรับแอปพลิเคชันเฉพาะได้อย่างง่ายดาย นั่นคือเราต้องการแอปพลิเคชันเซิร์ฟเวอร์

ไปช่วยเรื่องนี้ได้ไหม? เรารู้ว่าทำได้เพราะภาษารวบรวมแอปพลิเคชันเป็นไบนารีเดี่ยว มันเป็นข้ามแพลตฟอร์ม ใช้โมเดลการประมวลผลแบบขนานที่สวยงามและสวยงามมาก (พร้อมกัน) และไลบรารีสำหรับการทำงานกับ HTTP และในที่สุด ห้องสมุดโอเพ่นซอร์สและการผสานรวมนับพันแห่งก็จะมีให้เราใช้งานได้

ความยากในการรวมสองภาษาการเขียนโปรแกรมเข้าด้วยกัน

ขั้นตอนแรกคือการพิจารณาว่าแอปพลิเคชันตั้งแต่สองตัวขึ้นไปจะสื่อสารกันอย่างไร

เช่น การใช้ ห้องสมุดที่ยอดเยี่ยม Alex Palaestras สามารถใช้การแบ่งปันหน่วยความจำระหว่างกระบวนการ PHP และ Go (คล้ายกับ mod_php ใน Apache) แต่ไลบรารีนี้มีคุณสมบัติที่จำกัดการใช้งานในการแก้ปัญหาของเรา

เราตัดสินใจใช้แนวทางอื่นที่ใช้กันทั่วไปมากกว่า: เพื่อสร้างปฏิสัมพันธ์ระหว่างกระบวนการผ่านซ็อกเก็ต/ไปป์ไลน์ แนวทางนี้ได้พิสูจน์ความน่าเชื่อถือในช่วงหลายทศวรรษที่ผ่านมา และได้รับการปรับให้เหมาะสมในระดับระบบปฏิบัติการ

ประการแรก เราได้สร้างโปรโตคอลไบนารี่อย่างง่ายสำหรับการแลกเปลี่ยนข้อมูลระหว่างกระบวนการและการจัดการข้อผิดพลาดในการส่งข้อมูล ในรูปแบบที่ง่ายที่สุด โปรโตคอลประเภทนี้จะคล้ายคลึงกับ ตาข่าย с ส่วนหัวของแพ็กเก็ตขนาดคงที่ (ในกรณีของเราคือ 17 ไบต์) ซึ่งมีข้อมูลเกี่ยวกับประเภทของแพ็กเก็ต ขนาด และไบนารีมาสก์เพื่อตรวจสอบความสมบูรณ์ของข้อมูล

ในด้าน PHP ที่เราใช้ ฟังก์ชั่นแพ็คและด้าน Go - ห้องสมุด การเข้ารหัส/ไบนารี.

สำหรับเราดูเหมือนว่าโปรโตคอลเดียวไม่เพียงพอ - ดังนั้นเราจึงเพิ่มความสามารถในการโทร ไปที่บริการ net/rpc โดยตรงจาก PHP. สิ่งนี้ช่วยเราได้มากในการพัฒนาในภายหลัง เนื่องจากเราสามารถรวมไลบรารี Go เข้ากับแอปพลิเคชัน PHP ได้อย่างง่ายดาย ผลลัพธ์ของงานนี้สามารถดูได้ เช่น ในผลิตภัณฑ์โอเพ่นซอร์สอื่นๆ ของเรา โกริดจ์.

การกระจายงานให้กับผู้ปฏิบัติงาน PHP หลายคน

หลังจากใช้กลไกการโต้ตอบแล้ว เราก็เริ่มคิดถึงวิธีถ่ายโอนงานไปยังกระบวนการ PHP อย่างมีประสิทธิภาพสูงสุด เมื่องานมาถึง แอปพลิเคชันเซิร์ฟเวอร์จะต้องเลือกผู้ปฏิบัติงานอิสระเพื่อดำเนินการให้เสร็จสิ้น หากผู้ปฏิบัติงาน/กระบวนการยุติการทำงานด้วยข้อผิดพลาดหรือ "ตาย" เราจะกำจัดมันออกและสร้างอันใหม่เพื่อแทนที่ และหากผู้ปฏิบัติงาน/กระบวนการเสร็จสมบูรณ์ เราจะส่งคืนไปยังกลุ่มผู้ปฏิบัติงานที่พร้อมปฏิบัติงาน

RoadRunner: PHP ไม่ได้สร้างมาเพื่อตาย หรือ Golang มาเพื่อช่วยเหลือ

เพื่อจัดเก็บกลุ่มผู้ปฏิบัติงานที่เราใช้ ช่องบัฟเฟอร์เพื่อลบพนักงานที่ "เสียชีวิต" โดยไม่คาดคิดออกจากกลุ่ม เราได้เพิ่มกลไกในการติดตามข้อผิดพลาดและสถานะของพนักงาน

เป็นผลให้เราได้รับเซิร์ฟเวอร์ PHP ที่ใช้งานได้ซึ่งสามารถประมวลผลคำขอใด ๆ ที่นำเสนอในรูปแบบไบนารีได้

เพื่อให้แอปพลิเคชันของเราทำงานเป็นเว็บเซิร์ฟเวอร์ เราต้องเลือกมาตรฐาน PHP ที่เชื่อถือได้เพื่อแสดงคำขอ HTTP ที่เข้ามา ในกรณีของเราเราเพียงแค่ แปลง คำขอ net/http จากไปที่รูปแบบ PSR-7เพื่อให้เข้ากันได้กับเฟรมเวิร์ก PHP ส่วนใหญ่ที่มีอยู่ในปัจจุบัน

เนื่องจาก PSR-7 ถือว่าไม่เปลี่ยนรูป (บางคนอาจบอกว่าในทางเทคนิคแล้วเปลี่ยนไม่ได้) นักพัฒนาจึงต้องเขียนแอปพลิเคชันที่ไม่ได้ปฏิบัติต่อคำขอโดยพื้นฐานในฐานะเอนทิตีระดับโลก สิ่งนี้เข้ากันได้ดีกับแนวคิดของกระบวนการ PHP ที่มีอายุการใช้งานยาวนาน การใช้งานขั้นสุดท้ายของเราซึ่งยังไม่ได้ตั้งชื่อ มีลักษณะดังนี้:

RoadRunner: PHP ไม่ได้สร้างมาเพื่อตาย หรือ Golang มาเพื่อช่วยเหลือ

ขอแนะนำ RoadRunner - เซิร์ฟเวอร์แอปพลิเคชัน PHP ประสิทธิภาพสูง

งานทดสอบแรกของเราคือแบ็กเอนด์ API ซึ่งพบคำขอระเบิดอย่างไม่คาดคิดเป็นระยะๆ (บ่อยกว่าปกติมาก) แม้ว่าในกรณีส่วนใหญ่ nginx จะเพียงพอ แต่เราพบข้อผิดพลาด 502 เป็นประจำ เนื่องจากเราไม่สามารถปรับสมดุลระบบได้เร็วเพียงพอสำหรับการโหลดที่เพิ่มขึ้นที่คาดไว้

เพื่อแทนที่โซลูชันนี้ เราได้ปรับใช้เซิร์ฟเวอร์แอปพลิเคชัน PHP/Go ตัวแรกของเราในต้นปี 2018 และทันใดนั้นเราก็ได้รับผลอันน่าทึ่ง! ไม่เพียงแต่เรากำจัดข้อผิดพลาด 502 ได้อย่างสมบูรณ์ แต่เรายังสามารถลดจำนวนเซิร์ฟเวอร์ลงได้สองในสาม ซึ่งช่วยประหยัดเงินและความยุ่งยากให้กับวิศวกรและผู้จัดการผลิตภัณฑ์ได้มาก

ภายในกลางปี ​​เราได้ปรับปรุงโซลูชันของเราให้สมบูรณ์แบบ เผยแพร่บน GitHub ภายใต้ใบอนุญาต MIT และเรียกมันว่า RoadRunnerจึงตอกย้ำความเร็วและประสิทธิภาพอันน่าทึ่ง

RoadRunner สามารถปรับปรุงสแต็คการพัฒนาของคุณได้อย่างไร

ใบสมัคร RoadRunner อนุญาตให้เราใช้ Middleware net/http บนฝั่ง Go เพื่อทำการตรวจสอบ JWT ก่อนที่คำขอจะเข้าถึง PHP รวมถึงจัดการ WebSockets และการรวมสถานะทั่วโลกใน Prometheus

ด้วย RPC ในตัว คุณสามารถเปิด API ของไลบรารี Go ใดๆ สำหรับ PHP ได้โดยไม่ต้องเขียนส่วนขยายส่วนขยาย ที่สำคัญกว่านั้น RoadRunner สามารถใช้เพื่อปรับใช้เซิร์ฟเวอร์ที่ไม่ใช่ HTTP ใหม่ได้ ตัวอย่างรวมถึงการเรียกใช้ตัวจัดการใน PHP AWS แลมบ์ดาสร้างมือปราบคิวที่เชื่อถือได้และยังเพิ่มอีกด้วย ก.ร.ป ไปยังแอปพลิเคชันของเรา

ด้วยความช่วยเหลือของชุมชน PHP และ Go เราได้เพิ่มความเสถียรของโซลูชัน เพิ่มประสิทธิภาพแอปพลิเคชันได้มากถึง 40 เท่าในการทดสอบบางอย่าง ปรับปรุงเครื่องมือแก้ไขจุดบกพร่อง ใช้งานการบูรณาการกับเฟรมเวิร์ก Symfony และเพิ่มการรองรับ HTTPS, HTTP/ 2, ปลั๊กอิน และ PSR-17

ข้อสรุป

บางคนยังคงติดอยู่ในมุมมองที่ล้าสมัยของ PHP เนื่องจากเป็นภาษาที่ช้าและยุ่งยากซึ่งเหมาะสำหรับการเขียนปลั๊กอิน WordPress เท่านั้น คนเหล่านี้อาจกล่าวได้ว่า PHP มีข้อจำกัด: เมื่อแอปพลิเคชันมีขนาดใหญ่เพียงพอ คุณจะต้องเลือกภาษาที่ "เป็นผู้ใหญ่" มากขึ้นและเขียนฐานโค้ดที่สั่งสมมาหลายปีใหม่

ทั้งหมดนี้ฉันอยากจะตอบ: คิดใหม่อีกครั้ง เราเชื่อว่ามีเพียงคุณเท่านั้นที่สามารถกำหนดข้อจำกัดใดๆ สำหรับ PHP ได้ คุณสามารถใช้เวลาทั้งชีวิตในการกระโดดจากภาษาหนึ่งไปอีกภาษาหนึ่ง พยายามค้นหาภาษาที่ตรงกับความต้องการของคุณ หรือคุณสามารถเริ่มคิดว่าภาษาเป็นเครื่องมือ ข้อบกพร่องที่รับรู้ของภาษาเช่น PHP อาจเป็นสาเหตุของความสำเร็จ และถ้าคุณรวมเข้ากับภาษาอื่นเช่น Go คุณสามารถสร้างผลิตภัณฑ์ที่ทรงพลังได้มากกว่าการที่คุณถูกจำกัดให้ใช้เพียงภาษาเดียว

เมื่อทำงานร่วมกับ Go และ PHP ร่วมกัน เราบอกได้เลยว่าเรารักพวกเขา เราไม่ได้วางแผนที่จะเสียสละสิ่งใดสิ่งหนึ่งเพื่ออีกสิ่งหนึ่ง แต่ควรมองหาวิธีที่จะได้รับมูลค่าที่มากขึ้นจาก Dual Stack นี้

UPD: เรายินดีต้อนรับผู้สร้าง RoadRunner และผู้ร่วมเขียนบทความต้นฉบับ - ลาเชซิส

ที่มา: will.com

เพิ่มความคิดเห็น