RoadRunner: PHP ساخته نشده است که بمیرد، یا Golang برای نجات

RoadRunner: PHP ساخته نشده است که بمیرد، یا Golang برای نجات

هی هابر! ما در Badoo فعال هستیم کار بر روی عملکرد PHP، از آنجایی که ما یک سیستم نسبتاً بزرگ در این زبان داریم و مسئله عملکرد یک مسئله صرفه جویی در هزینه است. بیش از ده سال پیش ما PHP-FPM را برای این کار ایجاد کردیم که در ابتدا مجموعه ای از پچ ها برای PHP بود و بعداً وارد توزیع رسمی شد.

در سال‌های اخیر، PHP پیشرفت زیادی داشته است: جمع‌آوری زباله بهبود یافته است، سطح پایداری افزایش یافته است - امروزه می‌توانید دیمون‌ها و اسکریپت‌های طولانی مدت را در 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 7.4)

آیا PHP با 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 در آپاچی). اما این کتابخانه دارای ویژگی هایی است که استفاده از آن را برای حل مشکل ما محدود می کند.

ما تصمیم گرفتیم از یک رویکرد متفاوت و رایج تر استفاده کنیم: ایجاد تعامل بین فرآیندها از طریق سوکت ها / خطوط لوله. این رویکرد در دهه های گذشته ثابت کرده است که قابل اعتماد است و در سطح سیستم عامل به خوبی بهینه شده است.

برای شروع، ما یک پروتکل باینری ساده برای تبادل داده بین فرآیندها و مدیریت خطاهای انتقال ایجاد کردیم. در ساده ترین شکل خود، این نوع پروتکل مشابه است رشته شبکه с هدر بسته با اندازه ثابت (در مورد ما 17 بایت)، که حاوی اطلاعاتی در مورد نوع بسته، اندازه آن و یک ماسک باینری برای بررسی یکپارچگی داده است.

در سمت PHP ما استفاده کردیم عملکرد بستهو در سمت Go، کتابخانه رمزگذاری / باینری.

به نظر ما یک پروتکل کافی نیست - و ما قابلیت تماس را اضافه کردیم خدمات net/rpc go مستقیماً از PHP. بعدها، این به ما در توسعه کمک زیادی کرد، زیرا می‌توانستیم به راحتی کتابخانه‌های Go را در برنامه‌های PHP ادغام کنیم. نتیجه این کار را می توان به عنوان مثال در دیگر محصول منبع باز ما مشاهده کرد گوریج.

توزیع وظایف بین چندین کارمند PHP

پس از پیاده سازی مکانیسم تعامل، ما شروع به فکر کردن در مورد کارآمدترین راه برای انتقال وظایف به فرآیندهای PHP کردیم. هنگامی که یک کار می رسد، سرور برنامه باید یک کارگر آزاد را برای اجرای آن انتخاب کند. اگر یک کارگر/فرآیند با خطا یا "بمیر" خارج شود، از شر آن خلاص می شویم و یک فرآیند جدید برای جایگزینی ایجاد می کنیم. و اگر کارگر/فرایند با موفقیت به پایان رسیده باشد، آن را به مجموعه کارگرانی که برای انجام وظایف در دسترس هستند برمی گردانیم.

RoadRunner: PHP ساخته نشده است که بمیرد، یا Golang برای نجات

برای ذخیره سازی استخر کارگران فعال، استفاده کردیم کانال بافر شده، برای حذف کارگران غیرمنتظره "مرده" از استخر، مکانیزمی برای ردیابی خطاها و وضعیت کارگران اضافه کردیم.

در نتیجه، ما یک سرور PHP کارآمد داریم که قادر به پردازش هر درخواست ارائه شده به صورت باینری است.

برای اینکه برنامه ما به عنوان یک وب سرور شروع به کار کند، باید یک استاندارد PHP قابل اعتماد را برای نمایش هرگونه درخواست HTTP دریافتی انتخاب می کردیم. در مورد ما، ما فقط تبدیل درخواست net/http از Go to format PSR-7به طوری که با اکثر فریم ورک های PHP موجود امروزی سازگار است.

از آنجایی که PSR-7 تغییرناپذیر در نظر گرفته می‌شود (بعضی می‌گویند از نظر فنی اینطور نیست)، توسعه‌دهندگان باید برنامه‌هایی بنویسند که در اصل با درخواست به عنوان یک موجودیت جهانی برخورد نمی‌کنند. این به خوبی با مفهوم فرآیندهای طولانی مدت PHP مطابقت دارد. اجرای نهایی ما، که هنوز نامگذاری نشده است، به این صورت بود:

RoadRunner: PHP ساخته نشده است که بمیرد، یا Golang برای نجات

معرفی RoadRunner - سرور برنامه PHP با کارایی بالا

اولین کار آزمایشی ما یک backend API بود که به صورت دوره ای در درخواست های غیرقابل پیش بینی (بسیار بیشتر از حد معمول) منفجر می شود. اگرچه nginx در اکثر موارد کافی بود، اما ما مرتباً با 502 خطا مواجه می‌شدیم، زیرا نمی‌توانستیم سیستم را با سرعت کافی برای افزایش بار مورد انتظار متعادل کنیم.

برای جایگزینی این راه حل، ما اولین سرور برنامه PHP/Go خود را در اوایل سال 2018 مستقر کردیم. و بلافاصله یک اثر باور نکردنی دریافت کرد! نه تنها از شر خطای 502 به طور کامل خلاص شدیم، بلکه توانستیم تعداد سرورها را به میزان دو سوم کاهش دهیم و در هزینه ها و قرص های سردرد زیادی برای مهندسان و مدیران محصول صرفه جویی کنیم.

در اواسط سال، راه حل خود را بهبود بخشیده بودیم، آن را تحت مجوز MIT در GitHub منتشر کردیم و نام آن را گذاشتیم. مرغی شبیه فاخته تکزاس، بنابراین بر سرعت و کارایی باورنکردنی آن تاکید می شود.

چگونه RoadRunner می تواند پشته توسعه شما را بهبود بخشد

کاربرد مرغی شبیه فاخته تکزاس به ما اجازه داد تا از Middleware net/http در سمت Go استفاده کنیم تا تأیید JWT را قبل از رسیدن درخواست به PHP انجام دهیم، و همچنین WebSockets و وضعیت کل را در سطح جهانی در Prometheus مدیریت کنیم.

به لطف RPC داخلی، می توانید API هر کتابخانه Go را برای PHP بدون نوشتن بسته های افزونه باز کنید. مهمتر از آن، با RoadRunner می توانید سرورهای جدید غیر HTTP را مستقر کنید. به عنوان مثال می توان به اجرای کنترلرها در PHP اشاره کرد AWS لامبدا، ایجاد صف شکن قابل اعتماد و حتی اضافه کردن gRPC به برنامه های ما

با کمک جوامع PHP و Go، پایداری راه حل را بهبود بخشیده ایم، عملکرد برنامه را تا 40 برابر در برخی آزمایشات افزایش داده ایم، ابزارهای اشکال زدایی را بهبود بخشیده ایم، ادغام با چارچوب Symfony را پیاده سازی کرده و پشتیبانی از HTTPS، HTTP/2 را اضافه کرده ایم. پلاگین ها و PSR-17.

نتیجه

برخی از مردم هنوز درگیر مفهوم منسوخ PHP به عنوان یک زبان کند و غیرقابل استفاده هستند که فقط برای نوشتن افزونه برای وردپرس خوب است. این افراد حتی ممکن است بگویند که PHP دارای چنین محدودیتی است: وقتی برنامه به اندازه کافی بزرگ می شود، باید زبان "بالغ" تری را انتخاب کنید و پایه کد انباشته شده در طول سالیان متمادی را بازنویسی کنید.

به همه اینها می خواهم پاسخ دهم: دوباره فکر کن. ما معتقدیم که فقط شما هر گونه محدودیتی برای PHP تعیین می کنید. شما می توانید تمام زندگی خود را صرف انتقال از یک زبان به زبان دیگر کنید، سعی کنید مطابق با نیازهای خود مطابقت کامل پیدا کنید، یا می توانید زبان ها را به عنوان ابزار در نظر بگیرید. نقص های فرضی زبانی مانند PHP ممکن است در واقع دلیل موفقیت آن باشد. و اگر آن را با زبان دیگری مانند Go ترکیب کنید، محصولات بسیار قدرتمندتری نسبت به زمانی که به استفاده از یک زبان محدود می‌شدید ایجاد خواهید کرد.

پس از کار کردن با یک سری Go و PHP، می توان گفت که آنها را دوست داریم. ما قصد نداریم یکی را فدای دیگری کنیم - برعکس، ما به دنبال راه هایی برای به دست آوردن ارزش بیشتر از این پشته دوگانه خواهیم بود.

UPD: از خالق RoadRunner و نویسنده همکار مقاله اصلی استقبال می کنیم - لاچسیس

منبع: www.habr.com

اضافه کردن نظر