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 أتاح لنا إنشاء تطبيقات أكبر مع تحسينات في الأداء تصل إلى 40x. باستخدامه ، تمكنا من توسيع المنتجات الحالية المكتوبة بلغة 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 يبحثون عن طرق لحل هذه المشكلة لسنوات ، باستخدام تقنيات التحميل البطيء الذكية ، والأطر المصغرة ، والمكتبات المحسنة ، وذاكرة التخزين المؤقت ، وما إلى ذلك. ولكن في النهاية ، لا يزال يتعين عليك إعادة تعيين التطبيق بالكامل والبدء من جديد ، مرارًا وتكرارًا. (ملاحظة المترجم: سيتم حل هذه المشكلة جزئيًا مع ظهور التحميل المسبق في 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 ؛ وأخيرًا ، ستتوفر لنا آلاف المكتبات مفتوحة المصدر وعمليات الدمج.

صعوبات الجمع بين لغتين برمجة

بادئ ذي بدء ، كان من الضروري تحديد كيفية اتصال تطبيقين أو أكثر ببعضهما البعض.

على سبيل المثال، باستخدام مكتبة ممتازة أليكس Palaestras ، كان من الممكن مشاركة الذاكرة بين عمليات PHP و Go (على غرار mod_php في Apache). لكن هذه المكتبة بها ميزات تحد من استخدامها لحل مشكلتنا.

قررنا استخدام نهج مختلف وأكثر شيوعًا: لبناء التفاعل بين العمليات من خلال مآخذ / خطوط الأنابيب. أثبت هذا الأسلوب أنه يمكن الاعتماد عليه على مدار العقود الماضية وقد تم تحسينه جيدًا على مستوى نظام التشغيل.

بادئ ذي بدء ، أنشأنا بروتوكولًا ثنائيًا بسيطًا لتبادل البيانات بين العمليات ومعالجة أخطاء الإرسال. في أبسط أشكاله ، يشبه هذا النوع من البروتوكول netstring с رأس حزمة بحجم ثابت (في حالتنا 17 بايت) ، والتي تحتوي على معلومات حول نوع الحزمة وحجمها وقناع ثنائي للتحقق من سلامة البيانات.

في جانب PHP استخدمنا وظيفة الحزمة، وعلى جانب الذهاب ، المكتبة الترميز/ثنائي.

بدا لنا أن بروتوكولًا واحدًا لم يكن كافيًا - وقمنا بإضافة القدرة على الاتصال net / rpc go services مباشرة من 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 عالي الأداء

كانت مهمة الاختبار الأولى لدينا هي واجهة API الخلفية ، والتي تنفجر بشكل دوري بشكل غير متوقع (في كثير من الأحيان أكثر من المعتاد). على الرغم من أن nginx كان كافيًا في معظم الحالات ، فقد واجهنا بانتظام 502 خطأ لأننا لم نتمكن من موازنة النظام بالسرعة الكافية للزيادة المتوقعة في الحمل.

لاستبدال هذا الحل ، قمنا بنشر أول خادم تطبيقات PHP / Go في أوائل عام 2018. وحصل على الفور على تأثير لا يصدق! لم نتخلص فقط من الخطأ 502 تمامًا ، ولكننا تمكنا من تقليل عدد الخوادم بمقدار الثلثين ، مما وفر الكثير من المال وأقراص الصداع للمهندسين ومديري المنتجات.

بحلول منتصف العام ، قمنا بتحسين الحل الخاص بنا ونشرناه على GitHub بموجب ترخيص MIT وأطلقنا عليه اسم رود رنر، مما يؤكد سرعته وكفاءته المذهلة.

كيف يمكن لـ 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 ، يمكننا القول إننا نحبهم. نحن لا نخطط للتضحية بواحد من أجل الآخر - بل على العكس ، سنبحث عن طرق للحصول على قيمة أكبر من هذه المجموعة المزدوجة.

محدث: نرحب بمنشئ RoadRunner والمؤلف المشارك للمقال الأصلي - لاشيسيز

المصدر: www.habr.com

إضافة تعليق