
مرحبا يا هبر! نحن في Badoo
نعمل بنشاط
على أداء PHP ، نظرًا لأن لدينا نظامًا كبيرًا إلى حد ما بهذه اللغة ومسألة الأداء مسألة توفير المال. منذ أكثر من عشر سنوات ، أنشأنا لهذا PHP-FPM ، الذي كان في البداية مجموعة من التصحيحات لـ PHP ، ثم انتقل بعد ذلك إلى التوزيع الرسمي.
في السنوات الأخيرة ، حقق PHP خطوات كبيرة: لقد تحسن مُجمع القمامة ، وتحسن مستوى الاستقرار - يمكنك اليوم في PHP كتابة شياطين ونصوص طويلة العمر دون أي مشاكل. هذا سمح لـ Spiral Scout بالمضي قدمًا: لا تقوم RoadRunner ، على عكس PHP-FPM ، بمسح الذاكرة بين الطلبات ، مما يعطي زيادة في الأداء (على الرغم من أن هذا النهج يعقد عملية التطوير). نجرب الآن هذه الأداة ، ولكن ليس لدينا بعد نتائج يمكن مشاركتها. في انتظارهم كان أكثر متعة ،
ننشر ترجمة إعلان RoadRunner من Spiral Scout.إن مقاربة المقالة قريبة منا: عند حل مشكلاتنا ، نستخدم أيضًا في أغلب الأحيان مجموعة من PHP و Go ، والحصول على مزايا من اللغتين وعدم التخلي عن أحدهما لصالح الآخر.
استمتع!
على مدار السنوات العشر الماضية ، أنشأنا تطبيقات لكل من
شركات Fortune 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 يبحثون عن طرق لحل هذه المشكلة ، باستخدام طرق مدروسة للتحميل البطيء ، والأطر المصغرة ، والمكتبات المحسّنة ، وذاكرة التخزين المؤقت ، وما إلى ذلك. ولكن في النهاية ، لا يزال يتعين عليك إعادة ضبط التطبيق بالكامل والبدء من جديد مرارًا وتكرارًا.
(ملاحظة المترجم: سيتم حل هذه المشكلة جزئيًا مع ظهور التحميل المسبق في PHP 7.4)
هل يمكن لـ PHP استخدام Go للبقاء على قيد الحياة لأكثر من طلب واحد؟
يمكنك كتابة برامج PHP النصية التي ستدوم أطول من بضع دقائق (حتى ساعات أو أيام): على سبيل المثال ، مهام cron ، موزعي CSV ، قواطع قائمة الانتظار. كلهم يعملون وفق سيناريو واحد: يستخرجون المهمة ، يكملونها ، ينتظرون التالي. الرمز موجود في الذاكرة باستمرار ، مما يوفر مللي ثانية ثمينة ، نظرًا لأن العديد من الخطوات الإضافية مطلوبة لتنزيل الإطار والتطبيق.
لكن تطوير البرامج النصية الطويلة لم يكن بهذه البساطة. أي خطأ يقتل العملية بالكامل ، وتشخيص تسرب الذاكرة هو غضب ، وتصحيح الأخطاء باستخدام F5 لم يعد ممكنا.
تحسن الموقف من خلال إصدار PHP 7: ظهر مجمع موثوق للقمامة ، وأصبح التعامل مع الأخطاء أسهل ، وأصبحت ملحقات kernel محمية الآن من التسريبات. صحيح ، لا يزال المهندسون بحاجة إلى معالجة الذاكرة بعناية وتذكر مشاكل الحالة في الكود (هل هناك لغة يمكنك تجاهل هذه الأشياء بها؟). ومع ذلك ، في PHP 7 ، هناك عدد أقل من المفاجآت.
هل من الممكن أن نأخذ نموذجًا للعمل مع البرامج النصية لـ PHP طويلة العمر ، وقم بتكييفها للقيام بمهام أكثر تافهة مثل معالجة طلبات HTTP وبالتالي التخلص من الحاجة لتنزيل كل شيء من نقطة الصفر مع كل طلب؟
لحل هذه المشكلة ، كان من الضروري أولاً تطبيق تطبيق خادم قادر على قبول طلبات HTTP وإعادة توجيهها واحدًا تلو الآخر إلى عامل PHP ، دون قتله في كل مرة.
عرفنا أنه يمكننا كتابة خادم ويب بلغة PHP (PHP-PM) أو باستخدام C-extension (Swoole). وعلى الرغم من أن كل طريقة لها مزاياها الخاصة ، فإن كلا الخيارين لم يناسبنا - لقد أردت شيئًا أكثر. لم يكن هناك حاجة إلى خادم ويب فقط - كنا نتوقع أن نحصل على حل يمكن أن ينقذنا من المشاكل المرتبطة بـ "البداية الصعبة" في PHP ، والتي في الوقت نفسه يمكن تكييفها وتوسيعها بسهولة لتطبيقات محددة. وهذا هو ، كنا بحاجة إلى خادم التطبيق.
يمكن الذهاب مساعدة في هذا؟ علمنا أن ذلك ممكن ، لأن هذه اللغة تجمع التطبيقات في ملفات ثنائية واحدة ؛ انها عبر منصة. يستخدم نموذج التزامن الخاص به والأنيق للغاية ومكتبة للعمل مع HTTP ؛ وأخيرًا ، ستكون الآلاف من المكتبات والتكاملات مفتوحة المصدر متاحة لنا.
صعوبات في الجمع بين لغتين للبرمجة
بادئ ذي بدء ، كان من الضروري تحديد كيفية اتصال تطبيقين أو أكثر مع بعضهما البعض.
على سبيل المثال ، بمساعدة
مكتبة Alex Palaestras
الممتازة ، كان من الممكن تنفيذ مشاركة الذاكرة من خلال عمليات PHP و Go (على غرار mod_php في Apache). لكن هذه المكتبة بها ميزات تحد من استخدامها لحل مشكلتنا.
قررنا استخدام نهج مختلف أكثر شيوعًا: لبناء التفاعل بين العمليات من خلال مآخذ / خطوط الأنابيب. لقد أثبت هذا النهج على مدار العقود الماضية أنه يمكن الاعتماد عليه وتم تحسينه بشكل جيد على مستوى نظام التشغيل.
بادئ ذي بدء ، أنشأنا بروتوكولًا ثنائيًا بسيطًا لتبادل البيانات بين العمليات ومعالجة أخطاء الإرسال. في أبسط أشكاله ، يشبه بروتوكول من هذا النوع بروتوكول
الشبكة مع
رأس حزمة ذات حجم ثابت (في حالتنا ، 17 بايت) ، والذي يحتوي على معلومات حول نوع الحزمة وحجمها وقناع ثنائي للتحقق من سلامة البيانات.
على جانب PHP ، استخدمنا
وظيفة pack ، وعلى الجانب Go ، مكتبة
الترميز / الثنائية .
بروتوكول واحد لم يكن كافياً بالنسبة لنا -
وأضفنا القدرة على الاتصال بـ
net / rpc Go-services مباشرة من PHP . في وقت لاحق ، ساعدنا كثيرًا في التطوير ، حيث يمكننا بسهولة دمج مكتبات Go في تطبيقات PHP. يمكن رؤية نتيجة هذا العمل ، على سبيل المثال ، في منتجنا الآخر المفتوح المصدر
Goridge .
توزيع المهام بين العديد من عمال PHP
بعد تنفيذ آلية التفاعل ، بدأنا في التفكير في أفضل طريقة لنقل المهام إلى عمليات PHP. عند وصول المهمة ، يجب أن يختار خادم التطبيق عاملًا مجانيًا لإكماله. إذا تم إنهاء العامل / العملية بخطأ أو "مات" ، فسنتخلص منه وننشئ طريقة جديدة في المقابل. وإذا نجحت العملية / العملية ، فإننا نعيدها إلى مجموعة العمال المتاحة لإكمال المهام.

استخدمنا
قناة مخزنة لتخزين مجموعة من العمال النشطين ؛ لإزالة العمال "الموتى" بشكل غير متوقع من المجموعة ، أضفنا آلية لتتبع الأخطاء وحالة العمال.
نتيجة لذلك ، حصلنا على خادم PHP يعمل قادر على معالجة أي طلبات مقدمة في شكل ثنائي.
لكي يبدأ تطبيقنا العمل كخادم ويب ، كان علي اختيار معيار PHP موثوق لتقديم أي طلبات HTTP واردة. في حالتنا ، نقوم ببساطة
بتحويل طلب net / http من Go إلى تنسيق
PSR-7 بحيث يكون متوافقًا مع معظم أطر عمل PHP المتاحة اليوم.
نظرًا لأن PSR-7 تعتبر غير قابلة للتغيير (سيقول شخص ما أنها ليست كذلك من الناحية الفنية) ، يتعين على المطورين كتابة تطبيقات ، من حيث المبدأ ، لا يتعاملون مع الطلب ككيان عالمي. هذا يسير على ما يرام مع مفهوم عمليات PHP طويلة الأمد. بدا تنفيذنا النهائي ، الذي لم يتلق اسمًا بعد ، كما يلي:

كانت مهمة الاختبار الأولى هي واجهة برمجة تطبيقات واجهة برمجة التطبيقات ، والتي تسببت بشكل دوري في رشقات غير متوقعة من الطلبات (أكثر من المعتاد). على الرغم من وجود ميزات nginx كافية في معظم الحالات ، فقد واجهنا بانتظام خطأ 502 ، لأننا لم نتمكن من موازنة النظام بسرعة كافية للزيادة المتوقعة في الحمل.
لاستبدال هذا الحل ، في بداية عام 2018 ، قمنا بنشر أول خادم تطبيق PHP / Go. وعلى الفور حصلت على تأثير لا يصدق! لم نتخلص فقط من الخطأ 502 تمامًا ، ولكننا تمكنا أيضًا من تقليل عدد الخوادم بمقدار الثلثين ، مما يوفر لك الكثير من المال وأقراص الصداع للمهندسين ومديري المنتجات.
بحلول منتصف العام ، قمنا بتحسين حلنا ، ونشرناه على GitHub بموجب ترخيص MIT ، وقمنا بتسميته
RoadRunner ، مع التركيز على سرعته وكفاءته المذهلة.
كيف رود رنر يمكن تحسين التنمية الخاصة بك المكدس
سمح لنا استخدام
RoadRunner باستخدام Middleware net / http على الجانب "Go" لإجراء التحقق من JWT قبل أن يصل الطلب إلى PHP ، وكذلك لمعالجة WebSockets والدول التجميعية العالمية في Prometheus.
بفضل RPC المضمنة ، يمكنك فتح واجهة برمجة تطبيقات أي مكتبات GoP لـ PHP دون كتابة أغلفة الإضافات. الأهم من ذلك ، أن RoadRunner يمكنه نشر خوادم جديدة بخلاف HTTP. من الأمثلة على ذلك تشغيل معالجات
AWS Lambda في PHP ، وإنشاء
حل قوي لقائمة الانتظار
، وحتى إضافة
gRPC إلى تطبيقاتنا.
بمساعدة مجتمعات PHP و Go ، قمنا بزيادة استقرار الحل ، وفي بعض الاختبارات ، قمنا بزيادة أداء التطبيق إلى 40 مرة ، وتحسين أدوات تصحيح الأخطاء ، وتطبيق التكامل مع إطار Symfony ودعم إضافي لـ HTTPS و HTTP / 2 والمكونات الإضافية و PSR-17.
الخاتمة
لا يزال البعض مفتونًا بفكرة PHP التي عفا عليها الزمن كلغة بطيئة مرهقة ، ومناسبة فقط لكتابة المكونات الإضافية لـ WordPress. يمكن لهؤلاء الأشخاص حتى أن يقولوا أن PHP لديها مثل هذا القيد: عندما يصبح التطبيق كبيرًا بما يكفي ، عليك اختيار لغة أكثر نضجًا وإعادة كتابة قاعدة الشفرة التي تراكمت على مدار سنوات عديدة.
أود أن أجيب على كل هذا: فكر مرة أخرى. نحن نعتقد أنك وحدك هي التي تضع أي قيود على PHP. يمكنك قضاء حياتك بأكملها في التحول من لغة إلى أخرى ، في محاولة للعثور على مزيج مثالي مع احتياجاتك ، أو يمكنك البدء في إدراك اللغات كأدوات. قد تكون العيوب الظاهرة في لغة مثل PHP هي في الواقع أسباب نجاحها. وإذا قمت بدمجها مع لغة أخرى مثل Go ، فستقوم بإنشاء منتجات أكثر قوة مما لو كنت مقيدًا باستخدام لغة واحدة.
بعد العمل مع مجموعة من Go و PHP ، يمكننا القول أننا نحبهم. لا نعتزم التضحية بأحدهم لصالح الآخر - على العكس ، سوف نبحث عن طرق لاستخراج المزيد من الفوائد من هذه المجموعة المزدوجة.
محدث: مرحبًا بكم في مُبدع RoadRunner والمؤلف المشارك للمقالة الأصلية - Lachezis