كيف رأينا تقديم الخادم وما جاء منه

مرحبا بالجميع! على مدار العام ، تحولنا إلى React وفكرنا في كيفية التأكد من أن مستخدمينا لم ينتظروا وضع نماذج للعملاء ، ولكننا رأينا الصفحة في أسرع وقت ممكن. لهذا الغرض ، قررنا إجراء عرض من جانب الخادم (SSR - تقديم جانب الخادم) وتحسين مُحسّنات محرّكات البحث ، لأن ليس كل محركات البحث قادرة على تنفيذ JS ، وتلك التي تكون قادرة على قضاء بعض الوقت في التنفيذ ، كما أن وقت الزحف لكل موقع محدود.



اسمحوا لي أن أذكرك بأن تقديم الخادم هو تنفيذ شفرة JavaScript على جانب الخادم من أجل إعطاء العميل HTML جاهز. يؤثر هذا على أداء المستخدم المدرك ، وخاصة على الأجهزة الأبطأ وعلى الإنترنت البطيء. ليست هناك حاجة للانتظار حتى يتم تنزيل JS وتحليله وتنفيذه. يمكن للمتصفح عرض HTML فقط على الفور ، دون انتظار JSa ، يمكن للمستخدم قراءة المحتوى بالفعل.
وبالتالي ، يتم تقليل مرحلة الانتظار السلبي. بعد التقديم ، سيتعين على المتصفح فقط الانتقال إلى DOM النهائي ، والتحقق من مطابقته لما تم تقديمه
على العميل ، وإضافة مستمعي الحدث. وتسمى هذه العملية الماء . إذا كان هناك تباين بين المحتوى الموجود على الخادم وبين المحتوى الذي تم إنشاؤه بواسطة المستعرض ، في عملية الترطيب ، فسنحصل على تحذير في وحدة التحكم وجهاز عرض إضافي على العميل. لا ينبغي أن يكون هذا ، فمن الضروري التأكد من تطابق نتائج عرض الخادم والعميل. إذا تباعدوا ، فيجب التعامل مع هذا كخلل ، لأن هذا ينفي مزايا تقديم الخادم. إذا كان ينبغي أن يتباعد أي عنصر ، فأضف suppressHydrationWarning={true} .


بالإضافة إلى ذلك ، هناك تحذير واحد: لا توجد window على الخادم. يجب تنفيذ التعليمات البرمجية التي تصل إليها في أساليب دورة الحياة التي لا يتم استدعاء من جانب الخادم. بمعنى أنه لا يمكنك استخدام window في UNSAFE_componentWillMount () أو ، في حالة الخطافات ، uselayouteffect .


في الواقع ، تتلخص عملية التقديم من جانب الخادم في الحصول على الحالة الأولية من الواجهة الخلفية ، وتشغيلها من خلال renderToString() ، والتقاط renderToString() النهائي و HTML في الإخراج ، وإرساله إلى العميل.


في hh.ru ، لا يُسمح بإجراء عمليات رفع من عميل JS إلا في بوابة api في python. هذا هو للسلامة وموازنة الحمل. بيثون يذهب بالفعل إلى الخلفية اللازمة للبيانات ، وإعداده ويعطيها للمتصفح. يتم استخدام Node.js فقط لتقديم الخادم. وفقًا لذلك ، بعد إعداد البيانات ، يحتاج الثعبان إلى رحلة إضافية إلى العقدة ، في انتظار النتيجة وإرسال الاستجابة إلى العميل.


أولاً ، كان عليك اختيار خادم للعمل مع HTTP. توقفنا في كو . أحب بناء الجملة الحديثة مع await . Modularity عبارة عن برنامج وسيط خفيف الوزن ، إذا لزم الأمر ، يتم تثبيته بشكل منفصل أو يمكن كتابته بشكل مستقل. الخادم نفسه خفيف الوزن وسريع . نعم ، وكتبه كو من قبل فريق التطوير نفسه الذي يكتبون صريحة ، تجربة يأسر.


بعد أن علمنا كيفية طرح خدمتنا ، كتبنا أبسط الكود على KOA ، والتي كانت قادرة على إعطاء 200 ، وقمنا بتحميلها على المنتج. يبدو مثل هذا:


 const Koa = require('koa'); const app = new Koa(); const SERVER_PORT = 9400; app.use(async (ctx) => { ctx.body = 'Hello World'; }); app.listen(SERVER_PORT); 

في hh.ru ، تعيش جميع الخدمات في حاويات الإرساء. قبل الإصدار الأول ، تحتاج إلى كتابة قواعد تشغيل غير مألوفة ، يتم من خلالها طرح الخدمة في بيئات الإنتاج وعلى منصات الاختبار. كل مطور واختبار لديه بيئة الاختبار الخاصة به ، والتي تشبه إلى حد كبير همز. لقد أمضينا معظم وقتنا وكتابتنا في الطاقة. لقد حدث هذا بسبب قيام جهازي الواجهة الأمامية بهذا ، وهذه هي الخدمة الأولى على عقدة في hh.ru. كان علينا معرفة كيفية تحويل الخدمة إلى وضع التطوير ، والقيام بها بالتوازي مع الخدمة التي يتم تقديم العرض من أجلها. تسليم الملفات إلى حاوية. قم بتشغيل خادم مكشوف بحيث تبدأ حاوية عامل النقل دون انتظار الإنشاء. بناء وإعادة بناء الخادم جنبا إلى جنب مع الخدمة التي تستخدمها. تحديد مقدار ذاكرة الوصول العشوائي التي نحتاجها.


في وضع التطوير ، وفروا إمكانية إعادة الإنشاء التلقائي وإعادة التشغيل اللاحقة للخدمة عند تغيير الملفات المضمنة في الإنشاء النهائي. تحتاج العقدة إلى إعادة التشغيل لتحميل التعليمات البرمجية القابلة للتنفيذ. Webpack تراقب التغييرات ويبني . هناك حاجة إلى Webpack لتحويل ESM إلى CommonJS. لإعادة التشغيل ، أخذوا nodemon ، الذي يعتني الملفات التي تم جمعها.


ثم تعلمنا خادم التوجيه. لتحقيق التوازن الصحيح ، يلزمك معرفة مثيلات الخادم التي لا تزال حية. للتحقق من ذلك ، يذهب نبض القلب التشغيلي إلى /status مرة واحدة كل بضع ثوان ويتوقع الحصول على 200 استجابة. إذا كان الخادم لا يستجيب أكثر من عدد المرات المحددة في التكوين ، تتم إزالته من الموازنة. تبين أن هذه مهمة بسيطة ، بضعة أسطر وتوجيه جاهز:


 export default async function(ctx, next) { if (routeMap[ctx.request.path]) { routeMap[ctx.request.path](ctx); } else { ctx.throw(NOT_FOUND, getStatusText(NOT_FOUND)); } next(); } 

ونحن نجيب على 200 في عنوان URL الصحيح:


 export default (ctx) => { ctx.status = 200; ctx.body = '200'; }; 

بعد ذلك ، صنعنا خادمًا بدائيًا أعاد الحالة في <script> و HTML جاهز.


كان من الضروري التحكم في كيفية عمل الخادم. للقيام بذلك ، تحتاج إلى ربط التسجيل والمراقبة. لا تتم كتابة السجلات بلغة JSON ، ولكن لدعم تنسيق سجل خدماتنا الأخرى ، وخاصة Java. تم اختيار Log4js وفقًا للمعايير - إنه سريع وسهل التكوين ويكتب بالتنسيق الذي نحتاجه. هناك حاجة إلى تنسيق سجل شائع لتبسيط دعم المراقبة - لا حاجة لكتابة أدوات إضافية إضافية لتحليل السجلات. بالإضافة إلى السجلات ، ما زلنا نكتب أخطاء في الحراسة . لن أعطي رمز الحطابين ، إنه بسيط للغاية ، في الأساس ، هناك إعدادات.


بعد ذلك ، كان من الضروري توفير إيقاف تشغيل رشيق - عندما يمرض الخادم ، أو عندما يتدحرج الإصدار ، لا يقبل الخادم أي اتصالات واردة أخرى ، لكنه ينفذ جميع الطلبات المعلقة عليه. هناك العديد من الحلول الجاهزة للعقدة. أخذوا http-graceful-shutdown ، كل ما كان يجب القيام به هو التفاف استدعاء gracefulShutdown(app.listen(SERVER_PORT))


في هذه المرحلة ، حصلنا على حل جاهز للإنتاج. للتحقق من كيفية عمله ، قاموا بتشغيل تقديم الخادم لـ 5٪ من المستخدمين في صفحة واحدة. نظرنا إلى المقاييس ، وأدركنا أنها حسنت بشكل كبير FMP للهواتف المحمولة ، بالنسبة لأجهزة الكمبيوتر المكتبية ، لم تتغير القيمة. بدأوا في اختبار الأداء ، واكتشفوا أن خادمًا واحدًا يحمل 20 RPS تقريبًا (هذه الحقيقة كانت مسلية جدًا للجافيين). اكتشف أسباب هذا الأمر:


  • إحدى المشكلات الرئيسية التي تبين أنها صُنعت بدون NODE_ENV = إنتاج (وضعنا ENV الذي نحتاجه لبناء الخادم). في هذه الحالة ، يعطي التفاعل التجمع غير الإنتاجي ، والذي يعمل بحوالي 30٪ أبطأ.


  • لقد رفعنا نسخة العقدة من 8 إلى 10 ، وحصلنا على 20-25 ٪ أخرى من الأداء.


  • ما غادرنا للمرة الأخيرة هو إطلاق عقدة على عدة حبات. لقد شكنا في أن الأمر كان صعبًا للغاية ، ولكن هنا ، أيضًا ، تبين أن كل شيء كان عمليًا للغاية. العقدة لديه آلية مضمنة - الكتلة . يتيح لك تشغيل العدد المطلوب من العمليات المستقلة ، بما في ذلك العملية الرئيسية التي تقوم بتفريق المهام لها.



 if (cluster.isMaster) { cluster.on('exit', (worker, exitCode) => { if (exitCode !== SUCCESS) { cluster.fork(); } }); for (let i = 0; i < serverConfig.cpuCores; i++) { cluster.fork(); } } else { runApp(); } 

في هذا الرمز ، تبدأ العملية الرئيسية ، وتبدأ العمليات وفقًا لعدد وحدات المعالجة المركزية المخصصة للخادم. إذا تعطل أحد العمليات التابعة - رمز الخروج ليس 0 (قمنا بإيقاف تشغيل الخادم) ، فإن العملية الرئيسية تعيد تشغيله.
ويزيد الأداء بحوالي عدد وحدات المعالجة المركزية المخصصة للخادم.


كما كتبت أعلاه ، قضيت معظم الوقت في كتابة كتب اللعب الأصلية - حوالي 3 أسابيع. استغرق الأمر حوالي أسبوعين لكتابة SSR بالكامل ، ولمدة شهر تقريبًا أخذناها في الاعتبار. كل هذا تم بواسطة قوات من جبهتين ، دون خبرة مؤسسية في العقدة js. لا تخاف من عمل SSR ، والأهم من ذلك - لا تنس تحديد NODE_ENV=production ، فلا يوجد شيء معقد بشأنه. سوف المستخدمين وكبار المسئولين الاقتصاديين شكرا لك.

Source: https://habr.com/ru/post/ar445816/


All Articles