Tacticool موبايل مطلق النار ميتا خادم العمارة

حديث آخر مع محادثات Pixonic DevGAMM - هذه المرة من زملائنا في PanzerDog. قام مهندس البرمجيات الرئيسي في شركة Pavel Platto بتفكيك خادم meta-game الخاص بهندسة معمارية موجهة نحو الخدمات ، وأخبر عن الحلول والتقنيات التي تم اختيارها ، وما هي وكيف تم تطويرها ، والصعوبات التي واجهتها. نص التقرير وشرائح وروابط لخطب أخرى من الصورة المصغرة ، كما هو الحال دائمًا ، تحت القطع.


أولاً ، أريد عرض مقطع دعائي صغير للعبة:


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


المكدس التكنولوجي

يتم استضافة خادم meta على Amazon ويتم كتابته في Elixir. إنها لغة برمجة وظيفية مع نموذج فاعل للحساب. نظرًا لعدم وجود Ops ، يشارك المبرمجون في العملية ، ويتم وصف معظم البنية التحتية برمز باستخدام HashiCorp Terraform.

Tacticool حاليًا في مرحلة تجريبية مفتوحة ، وكان خادم meta قيد التطوير لأكثر من عام بقليل وكان قيد التشغيل لمدة عام تقريبًا. دعونا نرى كيف بدأ كل شيء.



عندما انضممت إلى الشركة ، كان لدينا بالفعل وظائف أساسية تم تنفيذها على أنها متجانسة على مزيج C / C ++ وتخزين PostageSQL. كان هذا التنفيذ مشاكل معينة.

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

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

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

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

  • ج # ؛
  • اذهب
  • إكسير.



كان C # في قائمة "التعارف" ، مثل يتم كتابة العميل وخادم اللعبة في Unity ، وكانت معظم تجربة الفريق مع لغة البرمجة هذه. تم اعتبار Go و Elixir لأنهما لغتان حديثتان وشائعتان إلى حد ما تم إنشاؤها لتطوير تطبيقات الخادم.

ساعدتنا مشاكل التكرار السابق في تحديد معايير تقييم المرشحين.

كان المعيار الأول هو سهولة العمل مع العمليات غير المتزامنة. في C # ، لم يظهر العمل المريح مع العمليات غير المتزامنة في المحاولة الأولى. وأدى ذلك إلى حقيقة أن لدينا "حديقة حيوانات" من الحلول التي ، في رأيي ، لا تزال قائمة إلى حد ما. في Go و Elixir ، تم أخذ هذه المشكلة في الاعتبار عند تصميم هذه اللغات ، كلاهما يستخدمان خيوط خفيفة الوزن (في Go هما goroutines ، في Elixir هما عمليات). تحتوي هذه التدفقات على مقدار حمل أصغر بكثير من مؤشرات ترابط النظام ، وبما أننا نستطيع إنشائها بعشرات ومئات الآلاف ، فإننا لا نأسف لحظرها.

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

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

وكان المعيار الأخير عدد المتخصصين. النتائج هنا واضحة. في النهاية ، اخترنا إكسير.

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



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

اكتشفنا اختيار التقنيات ، دعنا ننتقل إلى كيفية عمل خادم meta.



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

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

مع المخطط العام ، دعنا ننتقل إلى التفاصيل.



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

الآن القليل عن كيفية ترتيب الواجهة الأمامية.



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

الآن حول كيف تبدو الخلفية. في الوقت الحالي ، تتكون من خمس خدمات.



الأول يتعامل مع كل ما يتعلق بالحسابات - من مشتريات العملة داخل اللعبة إلى إكمال المهام. يعمل الثاني مع كل ما يتعلق بالمباريات - يتفاعل مباشرة مع GameLift وخوادم الألعاب. الخدمة الثالثة هي التسوق مقابل أموال حقيقية. الرابع والخامس مسؤولان عن التفاعلات الاجتماعية - أحدهما للأصدقاء ، والآخر للعبة الحزب.

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



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

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

كيف يحدث التفاعل مع GameLift؟ يتكون GameLift من عدة مكونات. من بين تلك التي نستخدمها ، هذه هي لعبة MatchMatch ، وهي قائمة انتظار لتحديد المواضع التي تحدد فيها منطقة معينة لاستضافة جلسة لعبة مع هؤلاء اللاعبين ، والأساطيل نفسها ، تتكون من خوادم اللعبة.



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

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



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

الآن دعنا ننتقل إلى البنية التحتية الإضافية التي نستخدمها.



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

وآخر خدمة نستخدمها أيضًا هي AWS Fargate. إنه يحفظنا من الاضطرار إلى إدارة مجموعة الآلات التي تعمل عليها حاويات السفن الخاصة بنا بشكل مستقل.



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

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

للتسجيل والمراقبة ، نستخدم بعض الخدمات.



لنبدأ بـ CloudWatch. هذه خدمة مراقبة تتدفق فيها المقاييس من جميع خدمات Amazon. لذلك ، قررنا إرسال المقاييس من خادم التعريف الخاص بنا هناك أيضًا. وللتسجيل ، نستخدم نهجًا مشتركًا على كل من العميل وخادم اللعبة وخادم التعريف. نرسل جميع السجلات إلى خدمة Amazonines Kinesis Firehose ، والتي بدورها تنقلها إلى Elasticseach و S3.

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

الآن القليل عن كيفية استخدام Terraform.



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

سأتحدث أيضًا عن كيفية تنفيذ التحديث بدون توقف.



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

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

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



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

أسئلة من الجمهور


- ولكن ألا يزعجك أن التعدين التلقائي يمكن أن يلتصق كثيرًا بسبب نوع من الخطأ وستحصل على الكثير من المال؟

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

- ما هي حدودك الحالية؟ بالنسبة للبنية التحتية الحالية كنسبة مئوية.

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

- وليس هناك حدود بعد؟

- نعم ، إنها مجرد 10-100 مرة أكثر من CCU. لا تفعل أقل.

- قلت أن لديك خطوطًا بين المقدمة والخلفية - هذا أمر غير معتاد. لماذا لا مباشرة؟

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

- هل قائمة الانتظار مستمرة إلى حد ما؟

- نعم. هذه خدمة Amazon SQS.

- فيما يتعلق بالصفوف: كم عدد القنوات التي تم إنشاؤها أثناء اللعبة؟ هل لديك عدد معين من القنوات لكل مباراة؟

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

- في هذه العمارة ، لن يكون هذا الحد لك؟

- كلا.

المزيد من المحادثات مع محادثات Pixonic DevGAMM


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


All Articles