ملاحظة عند كتابة هذا المقال ، لم يتضرر أي عنوان IP واحد ، المشروع الأصلي ، على الرغم من أنه كان مشبعًا بروح القرصنة (خادم مجاني للعبة مدفوعة!) ، لم ينتهك أي حقوق ، ولم يتم استخدام رمز صاحب حقوق الطبع والنشر هناك ، وكان الخادم يعتمد بالكامل على البحث عن عميل اللعبة الذي تم شراؤه بأمانة شعور المطور. يخبرنا هذا التأليف فقط عن التحديات التي واجهها المؤلف والطرق الأصلية لحلها ، سواء في المشروع القديم أو الحديث. أعتذر مقدمًا عن الأسلوب السردي للقصة ، بدلاً من سرد الحقائق ببساطة.
المقدمة
يمكنك أن تجادل بقدر ما تحب ذلك .Net ليست للخوادم ، ولكن بعد ذلك (والآن) بدت لي فكرة معقولة جدًا أنه يمكنك كتابة المنطق في شكل نصوص وتجميعها وتحميلها أثناء التنقل ، دون التفكير كثيرًا في تخصيص الذاكرة والتجميع الحطام والمؤشرات والمزيد. في الواقع ، يسمح لك هذا بتفويض البرمجة النصية لمنطق الأعمال للمطورين الأقل تأهيلاً ، ويقتصر فقط على Code Review. ولكن للقيام بذلك ، تحتاج إلى التأكد من أن النواة نفسها تعمل دون إخفاقات ، وقد بدأت تفشل حتى في الساعة 10-15 عبر الإنترنت ، في كل من عام 2004 وعام 2020.
في عام 2004 ، كان كل شيء يدور على Windows Server 2003 ، .Net 1.1 ، MSSQL 2000. تم توفير الخادم والاستضافة من قبل مزود Wnet ، ثم تم إنشاء خادم جديد باستخدام التبرعات من اللاعبين. لم يكن المشروع تجاريًا بحتًا ، وتم استخدام بعض الدخل الضئيل من اللافتات والحسابات المميزة للترقيات.يعمل الخادم الحديث على Mono تحت Debian في وضع التوافق .Net 4.7 ، مع MariaDB للبيانات المستضافة في سحابة Hetzner. لفترة طويلة بالفعل ، لا يوجد مثل هذا المثالي بعيون مشتعلة يعتقد أن الألعاب يجب أن تكون مجانية ، والتبرع وبيع عناصر اللعبة يقتل كل الاهتمام. الآن تحولت هذه الشخصية إلى اللون الرمادي ، وغيرت حماسه للتجربة وهو مقتنع بأن الشركة الناشئة يجب أن تجلب المتعة والدخل.
لكن الحكاية لا تتعلق بذلك ، بل تتعلق بالخوادم المكتوبة ذاتيًا ومشاكلها.
الفصل 1. الوباء
. , , , . , , . , , . Visual Studio, - , . EventLog .في عام 2020 ، كان تطبيق الخادم ، من حيث المبدأ ، مجرد تطبيق وحدة تحكم ، يعمل في شاشة منفصلة في Linux. لم يكن هناك المزيد من الخيارات لإطلاق Visual Studio ، ولكن المسجل أصبح متقدمًا جدًا على مر السنين ، وقد ظهر UnhandledExceptions مثل الأرانب في الشبكة ، ولم يكن هناك رمز أصلي من حيث المبدأ. والذي ، مع ذلك ، لم يحميك من الأعطال مع OOM و StackOverflowException. زاد عمق المكدس في حالة StackOverflowException عشرة أضعاف ، مما أدى إلى ملء مئات الكيلو بايت من السجل برسائل من نفس النوع ورفض كتابة تتبع مكدس عادي. ولكن على أي حال ، فإن إعادة التوجيه إلى >> log.txt أتاح بسرعة فهم من يقع اللوم وأين. ساعد بوت Telegram بشكل منفصل ، مما يشير إلى أن عملية الخادم قد توقفت.
— , Console.Out Console.Error. UnhandledExceptionHandler, . AutoFlush = true, , .
cmd — , . , , , - — , . - — .Net >> log.txt.
UnhandledExceptionHandler : OutOfMemoryException ( ), StackOverflowException Unmanaged . , — Access Violation - OOM.
Access Violation — ZLib ( ICSharpCode.SharpZipLib), OpenSSL ( SRP-6), MySQL ( System.Data MSSQL ).
, Socket.BeginReceive . .Net Thread Pool ( , IO Threads) , UnhandledExceptionHandler. , BeginReceive->EndReceive->BeginReceive , BeginReceive .
أدى كل هذا إلى تحسين الصورة بشكل كبير وبدأ الخادم في التعطل بمعدل أقل كثيرًا ، وغالبًا فقط عند نفاد الذاكرة.
ثم كان الأمر مجرد مسألة تقنية. أظهرت دراسة السجلات أن الفائض المكدس لم يظهر في القلب فحسب ، بل في منطق الأعمال: اصطدم الصاروخ بصاروخ أو صاروخ آخر ، وانفجر ، وأدى ذلك إلى انفجار الصاروخ الأول ، وهكذا في دائرة. إجمالاً ، هذه لحظة عمل عادية ، لكن ذلك عندما شعرت برغبة غريبة في القتال ضد شياطين الماضي المنسية منذ زمن طويل. ثم ظهر سبب جديد (أو قديم منسي منذ زمن طويل) للوباء - نقص الموارد.
الفصل 2. مسرور
— 256 , ! - , , , , — , OOM - . , — Visual Studio ( , ), WinDbg (), - dotTrace (). , . — , 1.7, . . 100%. , , , — ~100 . Maoni Stephens Rico Mariani GC, LOH (Large Object Heap) .Net. , (pin) , Gen 2, — LOH,يتسبب الخادم الحديث الذي يحتوي على ذاكرة أقل من 4 غيغابايت في ابتسامة ، ويمكنك إضافة 8-16 غيغابايت إضافية لحل السحابة ببضع نقرات وإعادة تشغيل واحدة. ومع ذلك ، عندما بدأت الذاكرة في التسرب وقفز حمل المعالج إلى 100-150٪ (بناءً على 800٪ لـ 8 مراكز) ، شعرت مرة أخرى كأنني طالب يبلغ من العمر 20 عامًا ، يحرق الجيجابايت والجيغا فلوب في صندوق النار الخاص بسيارة شرهة. كان غريبًا ، غير طبيعي ، وغبي. كان من المزعج بشكل خاص ، كما كان من قبل ، أن تستمر اللعبة في العمل بشكل طبيعي (وإن كان ذلك مع تأخر) ، ولكن لم ينقطع أي شيء. حسنًا ، حتى نفدت الذاكرة بالطبع.. — , , , (, .Net 1.1 Generics!). — , - , . Marshal.AllocHGlobal ( - , ). , , . , , , 100% CPU - . Interop WSASend/WSAReceive ( Windows , .Net) . - , .Net : BeginSend/BeginReceive , , 100% CPU.
, , , , , . , - 100% , !
, 2005 Workstation GC Server GC .Net 2.0 Preview. — , GC , 5-10% CPU.
, , Thread Pool Net 1.1 Workstation GC , ( !) ( 100% ).
BeginSend/BeginReceive Windows IOCP . , , , OOM 100% .
على مر السنين ، تمكنت الخيوط خفيفة الوزن (المعروفة أيضًا باسم Fibers) من الظهور والاختفاء بسبب عدم تمكننا من الوصول إلى خيوط النظام في .Net ، فقط إلى ما يسمى. خيوط مُدارة ، وعلى Mono لا يزال لا يوجد وصول إلى ProcessThread - لا يوجد سوى بذرة في الداخل. أصبح تشخيص الخيوط أكثر تعقيدًا ، لكنني الآن استخدمت مجموعة الخيوط الخاصة بي ، وتم حساب جميع سلاسل الرسائل وتسميتها ، وتم الاحتفاظ بإحصاءات دقيقة لكل منها ، وأي منها يؤدي حاليًا ، والمدة التي تستغرقها مهمة معينة. نتيجةً لذلك ، سرعان ما اتضح لتتبع أن المشاكل موجودة الآن في الكود الخاص بي ، وليس في النظام الأول ، وأظهرت إحصائيات الخيط أن zhor مرتبط بتنفيذ منطق الأعمال ، فقط بعض الإجراءات يتم تنفيذها 100 مرة أكثر مما ينبغي. الآن لم أكن مقيدًا بالموارد ،لذلك ، قمت بتزويد استدعاء كل برنامج نصي ومؤقت بهدوء بتسجيل إضافي ، وقمت بقياس وقت تنفيذ كل حدث ، وفي أسبوع من التجارب تمكنت من تحديد المشكلة بثقة. اتضح أن شخصية معينة من الشخصيات غير القابلة للعب كانت تحاول مهاجمة شخصية أخرى غير قابلة للعب وكلاهما عالق في الصخور ، لذلك لم يتمكنوا من التحرك وتوقفت محاولاتهم لإطلاق النار على بعضهم البعض على الفور بسبب عدم وجود خط البصر. لكن في نفس الوقت ، حاولوا حساب المسار ، في كل دورة من حساب السلوك (15 مللي ثانية) ، وبدأوا في إطلاق النار ، ولكن نظرًا لاستحالة إطلاق النار ، لم يتم إعادة تحميل الأسلحة وتكررت الدورة التالية. لعدة أيام من اللعبة ، تم تجنيد المئات من هذه الشخصيات واستهلكوا في النهاية جميع موارد الخادم. كان الحل هو تصحيح السلوك وتقليل المواقف المتوقفة ، وفي نفس الوقت وقت قصير لإعادة التحميل حتى في حالة اللقطات غير الناجحة.
ثم بدأ الخادم في التجميد.
الفصل 3. البرد
لم يكن خريف 2005 خريفًا سهلاً - فقد كان لدي وضع غير مؤكد في عملي ، وتضاعف إيجار شقتي فجأة. كنت سعيدًا فقط بخادم اللعبة - كان هناك بالفعل المئات من الخوادم عبر الإنترنت ، لكن المشكلة بدأت هناك أيضًا - بدأ العالم كله يتجمد. في أفضل الأحوال ، استمرت الأصوات في المشي أو عملت بعض أجهزة ضبط الوقت. وأحيانًا يتجمد كل شيء ، وتتوقف حركة المرور وكان عليك إنهاء تطبيق الخادم وتشغيله مرة أخرى. كما كان من قبل ، كان من المستحيل الاتصال بمصحح الأخطاء بخادم قيد التشغيل بسبب الاستهلاك الكبير والفرامل. لسبب ما ، تعطل Visual Studio ببساطة أو توقف من هذا.في أحد أيام أكتوبر الباردة من عام 2020 ، تعطل الوصول المخطط له من أجهزة البث المباشر لأن الخادم توقف فجأة. نجح التفويض ، لكن كان من المستحيل دخول العالم ، كان روبوت Telegram صامتًا. أظهر البحث السريع عن المشكلات أي شيء في السجلات ، ولم تكن هناك مشاكل في الذاكرة ، ولم يكن أي من الخيوط يتضور جوعاً. لقد توقف للتو. بعد أن قلت بصوت عالٍ عدة مرات شيئًا عن قطة من المصفوفة وامرأة ذات سلوك غير لائق ، بدأت أبحث عن طريق مسدود. بعد أن اشترت Microsoft شركة Miguel de Icaz و Xamarin ، أصبحت وثائق Mono مشهدًا مثيرًا للشفقة - فهي موجودة ولكنها ليست محدثة أو لا تقود إلى أي مكان. على سبيل المثال ، 3/4 من البيانات من الصفحةحول التصحيح الأحادي باستخدام gdb لا ينطبق ولا يعمل. تمكنت من الاتصال بالخادم المجمد عبر gdb ، لكن الأوامر تستدعي mono_pmip وأعطى البعض الآخر إجابات غير مفهومة ، معظمها حول أخطاء في بناء الجملة. من خلال بعض المعجزة ، أدركت أن gdb يريدني أن ألقي المعلمات ونتائج أوامر mono_ * لأنواع معينة ، وبالتالي تمكنت في النهاية من الحصول على قائمة من سلاسل الرسائل المجمدة في الحظر المتقاطع. لكن الأرقام الموجودة في القائمة لا تتطابق مع الأمر ps أو ManagedThreadId من الخادم. ساعد التسجيل الممتد ، الذي قمت به للعثور على المعالج يحترق ، كثيرًا - من خلاله تمكنت من فهم الحزم وأجهزة ضبط الوقت التي تم تنفيذها أخيرًا وبدأت تدريجيًا في تضييق دائرة المشتبه بهم. كشر ، لم يكن الحجب المتقاطع مع خيطين ، ولكن مع ثلاثة ، لذلك لم يكن من الممكن الحصول على صورة أكثر تفصيلاً.ثم تذكرت أشعل النار القديم وبدأت أبحث في رمز استخدام الأقفال. كما اتضح ، مرت العديد من عمليات إعادة البناء على مر السنين وتم استبدال SpinLock تدريجيًا بـ Monitor.Enter / Monitor.Exit ، وغالبًا بقفل بسيط. ثم فجأة لفتت انتباهياريك Gunnerson في المادة التي تقول أنه يمكنك أن تفعل ذلك أسهل بكثير: استخدام Monitor.TryEnter في كل مكان مع مهلة، وإذا فشل حظر، ثم رمي استثناء. هذه طريقة بسيطة للغاية وفعالة للغاية - إذا انتظرت مكالمة TryEnter في مكان ما لأكثر من 30 ثانية وانقطعت (وهذه التأخيرات ليست سمة من سمات المنطق) ، فيجب التحقق من هذا المكان والتحقق من الشخص الذي كان يمكن أن يأخذ مثل هذا الوقت الطويل ولم يعطه كائن القفل. عندما رشش الرماد على رأسي ، أدركت أنه كان بإمكاني تنظيف كل شيء بهذه الطريقة منذ 15 عامًا ، لم يكن من الضروري إعادة اختراع العجلة بحساب "عمق الحفرة". ولكن ربما كان ذلك للأفضل.
— , . , - . , - . SOS.dll. Son Of Strike WinDbg .Net , , . , .Net GC. - sos.dll 50. , , , . , — deadlock!
, . — . — , , , , ! , . SpinLock try/finally . , , — , SpinLock , , , , , . 8 , . , : , , “ ”. , . , , — .
, , Xeon 5130x2 8 . 2000, 2500, . , , , , -, . .
حسنًا ، ثم جاء المتسابق الرابع إلى مشروع جديد ، مرة واحدة إلى المحاكي. فقط لم يكن لديه الوقت ليصبح مشهورًا. ومع ذلك ، فإن وجود ما يصل إلى ثلاث مشاكل حرجة في بداية المشروع سرعان ما أطاح به. ولم تخرج اللعبة على الإطلاق في الاتجاه السائد. لكن هذا أيضًا ليس موضوعًا لهذه المقالة.
تستخدم المقالة الرسوم التوضيحية لفنان غير معروف Parsakoira مع التوقيع "ChoW # 227 :: VOTING :: 4 Horsemen of the Apocalypse" ، على الأرجح من الموقع المتوفى conceptart.com:
https://www.pinterest.com/pin/460141286926583086/
https : //www.pinterest.com/pin/490681321879914768/