يا هلا بالشباب الطيبة، محدثكم أبو عمر.
بتذكرها زي كأنها مبارح. كانت ليلة خميس، وكنا على وشك إطلاق تحديث جديد للمنصة. الأمور كانت ماشية زي الحلاوة، والقهوة السادة كانت عاملة شغلها. فجأة، بدأت التنبيهات توصل على تلفوني زي المطر. النظام بطيء، طلبات بتعلق، والمستخدمين بلشوا يشتكوا. قلبنا الدنيا فوقاني تحتاني، قعدنا ساعات واحنا بنحاول نعرف “شو القصة يا جماعة؟”.
بعد فحص وتحميص، اكتشفنا إن المشكلة كلها طالعة من خدمة صغيرة، خدمة مسؤولة عن تحويل أسعار العملات. يبدو إن الـ API الخارجي اللي بنعتمد عليه صار يعاني من بطء شديد. المشكلة الأكبر ما كانت في الخدمة نفسها، لكن في نظامنا إحنا. كل خدمة بتحتاج تحويل عملة كانت بتضلها تحاول وتستنى الرد من الخدمة البطيئة، لحد ما تستهلك كل مواردها وتوقع هي كمان. كانت زي أحجار الدومينو، واحد بيوقع بيوقع اللي بعده، والنظام كله على وشك الانهيار.
في هذيك الليلة، واحنا تحت ضغط رهيب، قررنا إن “خلص، بكفي”. لازم نلاقي حل جذري يمنع هاي الكارثة تتكرر. وهون كانت بداية رحلتنا مع المنقذ: نمط “قاطع الدائرة” أو الـ Circuit Breaker.
ما هو “جحيم الأعطال المتتالية”؟
قبل ما نحكي عن الحل، خلينا نفهم أصل المشكلة اللي كنا فيها، واللي بنسميها في عالم البرمجة “الأعطال المتتالية” (Cascading Failures).
تخيل معي عندك مجموعة خدمات مصغرة (Microservices) بتعتمد على بعضها. خدمة (أ) بتطلب معلومات من خدمة (ب). إذا خدمة (ب) توقفت عن العمل أو صارت بطيئة جداً، شو بصير؟
- خدمة (أ) بتبعت طلب لـ (ب) وبتضلها تستنى الرد.
- في نفس الوقت، طلبات تانية بتوصل لخدمة (أ) من خدمات أخرى أو من المستخدمين.
- خدمة (أ) بتفتح اتصال جديد لكل طلب وبتحاول تحكي مع (ب) اللي ما برد.
- بعد فترة قصيرة، كل “العمال” (Threads/Connections) في خدمة (أ) بكونوا مشغولين في انتظار خدمة (ب).
- النتيجة: خدمة (أ) نفسها بتصير غير قادرة على استقبال أي طلبات جديدة، وبتصير هي كمان “فاشلة” بنظر اللي بيتعاملوا معها.
وهكذا، الفشل ينتشر زي الوباء في النظام، من خدمة لخدمة، لحد ما كل شيء يوقف. هذا هو جحيم الأعطال المتتالية، واللي بخلي خطأ صغير في مكان واحد يسبب انهيار كامل للنظام.
المنقذ: نمط قاطع الدائرة (Circuit Breaker)
هنا يأتي دور البطل، نمط قاطع الدائرة. الفكرة بسيطة ومستوحاة من قاطع الدائرة الكهربائي اللي في كل بيت. لما يصير في حمل كهربائي زائد، القاطع “بفصل” عشان يحمي أجهزة البيت من الاحتراق ويمنع كارثة أكبر.
في عالم البرمجيات، قاطع الدائرة هو مجرد كائن برمجي (Object) بيجلس بين الخدمة الطالبة (Consumer) والخدمة المطلوبة (Provider)، وبيراقب حالة الاتصال بينهما. القاطع بمر بثلاث حالات رئيسية:
الحالة المغلقة (Closed)
هذا هو الوضع الطبيعي. الطلبات بتمر بشكل عادي من خلال القاطع إلى الخدمة المطلوبة. في هاي الأثناء، القاطع بسجل أي خطأ بصير (زي فشل الاتصال، استجابة خطأ من السيرفر 5xx، أو Timeout). إذا زاد عدد الأخطاء عن حد معين خلال فترة زمنية محددة (مثلاً، أكثر من 10 أخطاء في الدقيقة)، القاطع “بفصل” وبينتقل للحالة المفتوحة.
الحالة المفتوحة (Open)
لما القاطع يفصل، بصير زي الحاجز. أي طلب جديد بحاول يوصل للخدمة الفاشلة، القاطع بمنعه فوراً وبرجع خطأ مباشر للخدمة الطالبة بدون ما يحاول حتى يتصل بالخدمة المطلوبة. هذا هو الجوهر!
ملاحظة مهمة: هذا الإجراء السريع (Fail-Fast) هو اللي بنقذنا. بدل ما الخدمة الطالبة تضلها تستنى وتستهلك مواردها، هي بتحصل على رد فوري (خطأ) وبتقدر تتعامل معه (مثلاً، تعرض رسالة للمستخدم، أو تستخدم بيانات قديمة من الكاش). الأهم من هيك، احنا بنعطي الخدمة الفاشلة فرصة “تتنفس وترتاح شوي” بدون ما نضل نضغط عليها بطلبات جديدة.
بعد فترة زمنية محددة (Cooldown period)، القاطع بنتقل للحالة التالية عشان يجس النبض.
الحالة شبه المفتوحة (Half-Open)
في هاي الحالة، القاطع بحاول يكون حذر. بسمح لطلب واحد فقط بالمرور للخدمة المطلوبة كـ “جس نبض”.
- إذا نجح هذا الطلب: القاطع بفترض إن الخدمة رجعت للحياة، وبيرجع للحالة المغلقة (Closed)، والحياة بترجع طبيعية.
- إذا فشل هذا الطلب: القاطع بفهم إن المشكلة لسا موجودة، وبيرجع فوراً للحالة المفتوحة (Open) وبنتظر فترة أخرى قبل ما يحاول مرة ثانية.
خلينا نشوف الكود: مثال عملي
الكلام النظري حلو، بس “الحكي ما عليه جمرك”. خلينا نشوف كيف ممكن نطبق هالكلام بلغة C# مع مكتبة رائعة اسمها Polly، وهي مكتبة متخصصة في التعامل مع المرونة الرقمية (Resilience).
قبل قاطع الدائرة (الكود الكارثي):
// هذا الكود كان سبب سهرتنا الطويلة
public async Task<string> GetCurrencyDataAsync()
{
var client = new HttpClient();
// الطلب هنا سيبقى معلقاً إذا كانت الخدمة بطيئة، مما يستهلك الموارد
var response = await client.GetStringAsync("http://api.forex-service.com/latest");
return response;
}
بعد استخدام قاطع الدائرة مع Polly:
// تعريف سياسة قاطع الدائرة مرة واحدة في مكان مركزي
// يفصل بعد 5 أخطاء متتالية، ويبقى مفتوحاً لمدة 30 ثانية
var circuitBreakerPolicy = Policy
.Handle<Exception>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (exception, timespan) => { Console.WriteLine("القاطع فصل... المشكلة: " + exception.Message); },
onReset: () => { Console.WriteLine("القاطع رجع وصل... الأمور تمام!"); },
onHalfOpen: () => { Console.WriteLine("القاطع في حالة جس النبض..."); }
);
// استخدام السياسة عند كل طلب
public async Task<string> GetCurrencyDataWithBreakerAsync()
{
var client = new HttpClient();
// تنفيذ الطلب من خلال سياسة قاطع الدائرة
// إذا كان القاطع مفتوحاً، سيتم رمي BrokenCircuitException فوراً
var response = await circuitBreakerPolicy.ExecuteAsync(
() => client.GetStringAsync("http://api.forex-service.com/latest")
);
return response;
}
لاحظ كيف قمنا بتغليف الطلب الأصلي داخل circuitBreakerPolicy.ExecuteAsync. الآن، إذا فشلت الخدمة 5 مرات متتالية، أي محاولة تالية لتنفيذ هذا الكود ستفشل فوراً لمدة 30 ثانية، مما يحمي نظامنا من الانهيار.
نصائح من أبو عمر: كيف تطبق النمط صح؟
- جهز خطة بديلة (Fallback): لما القاطع يفصل، شو لازم يصير؟ أفضل حل هو إنك ما ترجع خطأ وبس. ممكن ترجع بيانات قديمة من الكاش (Cache)، أو قيمة افتراضية، أو رسالة لطيفة للمستخدم. هذا بحسن تجربة المستخدم بشكل كبير.
- اضبط الإعدادات بحكمة: عدد الأخطاء المسموح بها ومدة الفصل بتعتمد على طبيعة خدمتك. خدمة حساسة جداً ممكن تحتاج قاطع “يزعل بسرعة” (يفصل بعد خطأين مثلاً). خدمة أقل أهمية ممكن تكون متسامح معها أكثر. راقب وقيس وعدّل.
- التسجيل (Logging) هو روحك: سجل كل تغيير في حالة القاطع (متى فصل، متى رجع، متى جس النبض). هاي المعلومات ذهب وقت تحليل المشاكل. بدونها، أنت بتكون أعمى.
- اجمع بين الأنماط: قاطع الدائرة بشتغل بشكل رائع مع أنماط أخرى زي نمط إعادة المحاولة (Retry). بس انتبه! القاعدة هي: “حاول كم مرة، بعدين افصل”. لا تضل تحاول لما القاطع يكون مفتوح، هيك أنت بتناقض الهدف الأساسي.
الخلاصة: من الفوضى إلى المرونة الرقمية 💪
في عالم الأنظمة الموزعة والخدمات المصغرة، الفشل ليس احتمالاً، بل هو حقيقة واقعة ومؤكدة. الهدف ليس بناء أنظمة لا تفشل أبداً، بل بناء أنظمة قادرة على تحمل الفشل والتعافي منه بأناقة. نمط قاطع الدائرة هو واحد من أهم الأدوات في صندوق العدة لتحقيق هذه المرونة الرقمية.
منذ تلك الليلة، أصبح نمط قاطع الدائرة جزءاً لا يتجزأ من كل خدمة نطورها. تعلمنا الدرس بالطريقة الصعبة، لكنه كان درساً قيماً. حوّلنا الفشل من كارثة توقف العمل إلى مجرد تنبيه على الشاشة، تنبيه يخبرنا أن نظامنا يقوم بعمله على أكمل وجه: حماية نفسه بنفسه.
نصيحتي الأخيرة لكم: ما تخافوا من الفشل، بل استعدوا له. ابنوا أنظمتكم لتكون قادرة على “التنفس” بعد الصدمة، وقادرة على النهوض مجدداً. ويعطيكم العافية.