التشغيل البيني الآمن في الأنظمة الموزعة



مرحباً هبر!

اسمي أليكسي سولودكي ، وأنا مطور PHP في Badoo. واليوم سوف أشارك نسخة نصية من حديثي في ​​لقاء Badoo PHP الأول. يمكن العثور على فيديو لهذا والتقارير الأخرى من mitap هنا .

أي نظام يتكون من مكونين على الأقل (وإذا كان لديك كل من PHP وقاعدة بيانات ، فهذا مكونان) ، يواجه فئات كاملة من المخاطر في التفاعل بين هذه المكونات.

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

الواجهة الخلفية لدينا عبارة عن مجموعة متجانسة من PHP تتفاعل مع العديد من الخدمات (يوجد حاليًا حوالي 50 منها). نادرا ما تتفاعل الخدمات مع بعضها البعض. لكن المشاكل التي أتحدث عنها في المقالة تتعلق أيضًا بهندسة الخدمات الصغيرة. في الواقع ، في هذه الحالة ، تتفاعل الخدمات بنشاط مع بعضها البعض ، وكلما زاد التفاعل لديك ، زادت المشاكل التي تواجهها.

ضع في اعتبارك ما يجب فعله عند تعطل الخدمة أو التعتيم ، وكيفية تنظيم مجموعة المقاييس وما يجب فعله عندما لا يوفر لك كل ما سبق.

تعطل الخدمة


عاجلاً أم آجلاً ، سيقع الخادم الذي تم تثبيت الخدمة عليه. سيحدث بالتأكيد ، ولا يمكنك الدفاع عنه - فقط قلل من الاحتمالية. يمكنك أن تخذل من قبل الأجهزة والشبكة والرمز والنشر غير الناجح - أي شيء. وكلما زاد عدد الخوادم لديك ، كلما حدث ذلك.

كيف تجعل خدماتك تعيش في عالم تتعطل فيه الخوادم باستمرار؟ التكرار هو نهج عام لحل هذه الفئة من المشاكل.

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

دعونا نفكر في المشاكل النموذجية للتكرار باستخدام أبسط مخطط كمثال:


يتواصل التطبيق مع السيد حصريًا ، بينما في الخلفية ، بشكل غير متزامن ، يتم نقل البيانات إلى الرقيق. عندما يتعطل السيد ، سننتقل إلى العبد ونستمر في العمل.



بعد استعادة السيد ، نصنع عبدًا جديدًا منه ، ويتحول العبد القديم إلى سيد.

المخطط بسيط ، ولكن حتى لديه العديد من الفروق الدقيقة المميزة لأي مخططات زائدة عن الحاجة.

تحميل


لنفترض أن خادمًا واحدًا من المثال أعلاه يمكنه تحمل 100 كيلوبايت في الثانية. الآن الحمل هو 60k RPS ، ويعمل كل شيء مثل الساعة.

ولكن مع مرور الوقت ، يزداد الحمل على التطبيق ، وبالتالي الحمل على المعلم. قد ترغب في موازنتها عن طريق نقل جزء من القراءة إلى عبد.

تبدو جيدة. يحمل الحمل ، لم يعد الخادم خاملاً. لكن هذه فكرة سيئة. من المهم أن تتذكر لماذا قمت في البداية برفع العبد - للتبديل إليه في حالة وجود مشاكل مع العبد الرئيسي. إذا بدأت في تحميل كلا الخادمين ، فعندما يتعطل سيدك - وتعطل عاجلاً أم آجلاً - سيكون عليك تبديل حركة المرور الرئيسية من الخادم الرئيسي إلى خادم النسخ الاحتياطي ، وقد تم تحميله بالفعل. مثل هذا التحميل الزائد سيجعل نظامك بطيئًا بشكل رهيب ، أو سيعطله تمامًا.

البيانات


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

في حالة كون الخدمة ذات حالة ، لا يمكننا القيام بذلك بعد الآن. تحتاج إلى التفكير في كيفية تخزين نفس البيانات في جميع مثيلات خدمتنا بحيث تظل متسقة.

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

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

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

الاستنتاجات


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

خدمة الصمت


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

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

لنفترض أن هذه الحالة تمت معالجتها باستخدام nginx مع عدد ثابت من العاملين في PHP FPM (مع عشرة ، على سبيل المثال). إذا تمت معالجة كل طلب لمدة 20 مللي ثانية تقريبًا ، فيمكن بمساعدة الحسابات البسيطة أن يفهم أن نظامنا قادر على معالجة حوالي خمسمائة طلب في الثانية.

ماذا يحدث عندما تبدأ إحدى هذه الخدمات الأربعة في الظهور بشكل حاد ، وتزداد معالجة الطلبات إليها من 20 مللي ثانية إلى مهلة 1000 مللي ثانية؟ من المهم أن نتذكر أنه عندما نعمل مع الشبكة ، يمكن أن يكون التأخير كبيرًا بشكل لا نهائي. لذلك ، يجب عليك دائمًا تعيين مهلة (في هذه الحالة ، يساوي ثانية).

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

ولكن ما هي المشكلة الحقيقية هنا؟ والحقيقة هي أنه عندما تتم معالجة كل طلب في الثانية ، ينخفض ​​معدل النقل بشكل مأساوي إلى عشرة طلبات في الثانية. ولن يتمكن المستخدم الحادي عشر من الحصول على رد ، حتى لو طلب صفحة لا ترتبط بأي شكل من الأشكال بخدمة باهتة. فقط لأن العمال العشرة ينتظرون مهلة ، ولا يمكنهم معالجة الطلبات الجديدة.

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

الحل


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

لقد كتبنا تنفيذنا لهذا النمط. ولكن ليس لأننا نحب كتابة التعليمات البرمجية ، ولكن لأنه عندما قمنا بحل هذه المشكلة منذ سنوات عديدة ، لم تكن هناك حلول جاهزة.

الآن سأوضح بشكل عام حول تنفيذنا وكيف يساعد على تجنب هذه المشكلة. ويمكن سماع المزيد عنها وخلافاتها عن الحلول الأخرى في تقرير لميخائيل كورمايف حول Highload Siberia في أواخر يونيو. نسخة نص تقريره ستكون أيضا على هذه المدونة.

يبدو شيء مثل هذا:

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

إذا تفاقم الوضع ، ووصل عدد العمال المثبطين إلى قيمة العتبة الثانية - في إنتاجنا حوالي 10 ٪ - قمنا بتقليص هذا المضيف تمامًا. بتعبير أدق ، تستمر الخدمة في العمل بالفعل ، لكننا نتوقف عن إرسال الطلبات إليها. يرفضهم متصفح الدائرة ويعطي العمال خطأ على الفور ، كما لو كانت الخدمة تكذب.

من وقت لآخر ، نتخطى تلقائيًا طلبًا من أحد العاملين لمعرفة ما إذا كانت الخدمة قد ظهرت في الحياة أم لا. إذا أجاب بشكل ملائم ، فإننا ندرجه مرة أخرى في العمل.

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


تطبيقات


لحسن الحظ ، Open Source لا يقف ساكنًا ، واليوم يمكنك أن تأخذ حلًا جاهزًا على Github.

هناك طريقتان رئيسيتان لتنفيذ قاطع الدائرة: مكتبة على مستوى الكود ، وخفي مستقل يقوم الوكلاء بطلبه من خلاله.

يعد الخيار مع المكتبة أكثر ملاءمة إذا كان لديك متراصة رئيسية واحدة في PHP ، والتي تتفاعل مع العديد من الخدمات ، ولا تتواصل الخدمات تقريبًا مع بعضها البعض. فيما يلي بعض عمليات التنفيذ المتاحة:


إذا كان لديك العديد من الخدمات بلغات مختلفة ، وكلها تتفاعل مع بعضها البعض ، فسيتعين تكرار الخيار على مستوى الرمز بكل هذه اللغات. هذا غير مريح في الدعم ، ويؤدي في النهاية إلى اختلافات في عمليات التنفيذ.

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

إليك بعض الخيارات (الوظيفة أكثر ثراءً ، ولكن هناك قاطع الدائرة أيضًا):


الاستنتاجات


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

الرصد والقياس عن بعد


ماذا يعطي


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


ما يجب قياسه


مقاييس التكامل

نظرًا لأننا نتحدث عن التفاعل بين الخدمات ، فإننا نراقب كل ما هو ممكن فيما يتعلق بتوصيل الخدمة مع التطبيق. على سبيل المثال:

  • عدد الطلبات ؛
  • وقت معالجة الطلب (بما في ذلك النسب المئوية) ؛
  • عدد أخطاء المنطق ؛
  • عدد أخطاء النظام.

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

المقاييس الداخلية

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

دعونا نرى المقاييس التي يمكن جمعها باستخدام memcached كمثال:

  • نسبة الضرب / الخطأ ؛
  • زمن الاستجابة لعمليات مختلفة ؛
  • RPS للعمليات المختلفة ؛
  • انهيار نفس البيانات على مفاتيح مختلفة ؛
  • مفاتيح تحميل أعلى ؛
  • جميع المقاييس الداخلية المعطاة بواسطة الأمر stats.


كيف تفعل ذلك


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


بدلاً من ذلك ، يمكنك دائمًا تثبيت Zabbix أو Grafana أو أي حل آخر مستضاف ذاتيًا على جهازك.

الاستنتاجات


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

تذكار موري


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

على سبيل المثال ، توجد في Badoo صفحة "الأشخاص القريبون". هناك ، يبحث المستخدمون عن أشخاص آخرين بالقرب منهم للدردشة معهم.



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



معظم المستخدمين لا يعرفون حتى عن هذه الحقيقة: فريقنا يستجيب بسرعة ، وسرعان ما تظهر الكتلة مرة أخرى.

ولكن ليس كل الوظائف التي يمكننا إزالتها بهدوء. إذا فقدنا الخدمة المسؤولة عن الجزء المركزي من الصفحة ، فلن يعمل هذا للاختباء. لذلك ، من المهم إخبار المستخدم بلغته بما يحدث.



من المستحسن أيضًا أن لا يؤدي فشل خدمة واحدة إلى فشل متتالي. لكل خدمة ، يجب كتابة الرمز الذي يعالج سقوطها ، وإلا قد يتعطل التطبيق ككل.

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





ليموت على حق


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

الملخص


لدمج الخدمة الجديدة بشكل موثوق في النظام ، نكتب واجهة برمجة تطبيقات مجمعة خاصة حولها في Badoo ، والتي تتولى المهام التالية:

  • موازنة الحمل
  • المهلات
  • تجاوز الفشل المنطقي
  • قاطع الدائرة
  • الرصد والقياس عن بعد ؛
  • منطق التفويض ؛
  • تسلسل البيانات وإلغاء تسلسلها.

من الأفضل التأكد من أن جميع هذه العناصر مغطاة أيضًا في طبقة التكامل. خاصة إذا كنت تستخدم عميل API مفتوح المصدر جاهز. من المهم أن تتذكر أن طبقة التكامل هي مصدر زيادة خطر فشل التطبيق المتتالي الخاص بك.

شكرا لكم على اهتمامكم!

الأدب

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


All Articles