مبادئ التزامن في Go تجعل معالجة المهام غير المتزامنة تبدو بسيطة للغاية. الأنماط الافتراضية تعمل في الأمثلة التعليمية لكنها تسرب goroutines وتفقد المهام في أعباء عمل SaaS الإنتاجية.
مبادئ goroutines والقنوات في Go أدوات قوية حقاً لتصميم الأنظمة المتزامنة. وهي أيضاً مسؤولة عن بعض أصعب الأخطاء الإنتاجية التي واجهناها في أنظمة SaaS الخلفية. المشكلة ليست في المبادئ نفسها بل في الفجوة بين كيف تبدو في أمثلة الوثائق وكيف تتصرف تحت أنماط الحمل التي تولدها منتجات SaaS الحقيقية.
لماذا تفشل goroutines الخام في أعباء عمل SaaS الإنتاجية
نقطة الدخول لمعظم التزامن في Go الإنتاجي تبدو هكذا: معالج HTTP يستقبل طلباً، يقرر أن بعض العمل يجب أن يحدث بشكل غير متزامن، ويولّد goroutine.
هذا يعمل في التطوير. يعمل في الإنتاج المبكر. يفشل عند الحجم لعدة أسباب.
أولاً، لا يوجد حد لإنشاء goroutine. انفجار من 10,000 طلب يولّد 10,000 goroutine. كل goroutine يحتاج مساحة ذاكرة مكدس وعادةً اتصالاً بقاعدة البيانات. إذا كان مجمع اتصالات قاعدة البيانات يحتوي على 20 اتصالاً و10,000 goroutine تحاول الحصول على واحد، تحصل على 9,980 goroutine محجوبة في الانتظار.
ثانياً، لا يوجد ضغط رجعة. عندما يكون النظام محملاً بشكل زائد، يقبل المزيد من العمل بدلاً من إشارة للمُستدعي بأنه يجب أن يتباطأ.
ثالثاً، لا توجد رؤية. لا يمكنك بسهولة ملاحظة كم goroutine تعمل، كم مهمة تنتظر، أو ما إذا كانت المهام تُفقد.
رابعاً، لا يوجد إغلاق سلس. عندما تستقبل العملية SIGTERM، قد تُقتل goroutines الجارية في منتصف التنفيذ.
بناء مجمع عمال محدود من الصفر
مجمع العمال يحل مشكلة انفجار goroutine بإنشاء عدد ثابت من goroutines عند بدء التشغيل وتوجيه كل العمل من خلالها.
عمق قناة المهام يحدد كم مهمة يمكن أن تنتظر قبل أن تبدأ Submit في الرفض. اضبطه بناءً على تحملك للكمون وميزانية الذاكرة.
الإغلاق يغلق قناة المهام وينتظر جميع العمال لاستنزافها قبل العودة. هذا إغلاق نظيف: لا مهام جارية تُهمَل عند خروج العملية.
ضغط الرجعة في القنوات: كيف ترفض العمل بأناقة
ضغط الرجعة هو الإشارة التي يرسلها النظام عندما يكون بالسعة الكاملة. بدون ضغط رجعة، يقبل النظام عملاً لا يستطيع معالجته.
للمعالجات التي تواجه المستخدم، فكر فيما إذا كان العمل يحتاج حقاً إلى أن يكون غير متزامن. إذا كان المستخدم ينتظر نتيجة وكانت القائمة ممتلئة، إعادة خطأ أفضل من جعل المستخدم ينتظر إلى أجل غير مسمى.
معالجة الأخطاء ومنطق إعادة المحاولة
مهام مجمع العمال تفشل. الشبكة غير متاحة. استعلام قاعدة البيانات يعيد خطأ تعارض. خادم API خارجي يعيد 503. كيف يتعامل المجمع مع هذه الإخفاقات يحدد ما إذا كان النظام موثوقاً.
النمط الأساسي هو إعادة المحاولة مع backoff أسي وعدد أقصى من المحاولات.
ليست كل الأخطاء قابلة لإعادة المحاولة. انتهاك قيد فريد في قاعدة البيانات لن يحل مع إعادة المحاولة. هذه يجب تسجيلها كإخفاقات دائمة وعدم إعادة محاولتها.
للمهام التي يجب ألا تُفقد أبداً حتى بعد استنزاف المحاولات، اكتب المهمة الفاشلة إلى جدول dead letter في قاعدة البيانات.
مراقبة صحة المجمع في الإنتاج
مجمع عمال لا يمكنك مراقبته خطر. الحد الأدنى من القياس لمجمع عمال إنتاجي هو ثلاثة مقاييس: عمق القائمة، وعدد العمال النشطين، ومدة المهمة.
افضح هذه القيم عبر نقطة نهاية /metrics أو ادفعها إلى Prometheus. أنذر عندما يتجاوز عمق القائمة 80% من السعة. أنذر عندما يساوي عدد العمال النشطين باستمرار إجمالي العمال.
لمنتجات SaaS التي تخدم الشركات في لبنان ومنطقة MENA، وجود لوحة Grafana تظهر عمق القائمة بمرور الوقت لكل نوع من مجمعات العمال مفيد للغاية أثناء الحوادث.
تنسيق الإغلاق بشكل نظيف
الإغلاق السلس لنظام قائم على مجمع عمال يتطلب تنسيقاً بين خادم HTTP والمجمع وعمال المجمع.
تسلسل الإغلاق مهم. أوقف خادم HTTP أولاً حتى لا تُرسَل مهام جديدة. ثم استنزف المجمع. هذا يضمن معالجة جميع المهام المقبولة قبل خروج العملية.
الدروس الرئيسية من بيئة الإنتاج
لا تستخدم أبداً goroutine spawning غير محدودة لمعالجة المهام الإنتاجية. استخدم دائماً مجمع عمال محدوداً.
صمّم ضغط الرجعة قبل أن تحتاجه. مسار رفض Submit مهم مثل المسار السعيد.
تصنيف الأخطاء مهم. الأخطاء القابلة للإعادة يجب إعادة محاولتها. الأخطاء الدائمة يجب أن تفشل بسرعة وتنذر.
نظام dead letter يمنع فقدان المهام الصامت. كل مهمة فشلت في المعالجة يجب أن تكون قابلة للاسترداد.



