خدمة واحدة بطيئة شلّت النظام بأكمله: كيف أنقذني نمط ‘قاطع الدائرة’ (Circuit Breaker) من تأثير الدومينو؟

ليلة الجمعة البيضاء التي كادت أن تكون سوداء

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

انطلقت الحملة الساعة 12 بالليل، والأمور كانت ماشية زي الحلاوة أول ساعة. الطلبات بتنزل، المستخدمين مبسوطين، والفريق كله بتابع لوحات المراقبة (Dashboards) وعيونه بتلمع من الفرحة. فجأة، وبدون سابق إنذار، بدأت التنبيهات توصلنا زي المطر. “API Latency High”, “5xx Server Errors”, “CPU Usage 100%”.

فتحنا الموقع… بطيء، بطيء لدرجة الشلل. الصفحات ما بتفتح، أو بتاخد دقايق عشان تحمّل. المستخدمين بلشوا يشتكوا على السوشيال ميديا. الأدرينالين ضرب في العالي، والتوتر صار سيد الموقف. وين المشكلة؟ كل الخدمات الأساسية (المنتجات، سلة الشراء، الدفع) كانت تبدو سليمة على لوحات المراقبة الخاصة فيها.

بعد حفر وتنقيب في السجلات (Logs) لمدة نص ساعة كانت زي الدهر، اكتشفنا المصيبة. خدمة “التوصيات الشخصية” (Personalized Recommendations)، وهي خدمة ثانوية المفروض تعرض للمستخدم منتجات قد تعجبه، كانت بتعاني من بطء شديد بسبب استعلام معقد على قاعدة البيانات. هاي الخدمة مش أساسية، يعني لو وقفت، الموقع بضل شغال.

لكن اللي صار كان كارثي. الخدمة الرئيسية اللي بتعرض صفحة تفاصيل المنتج كانت بتستدعي خدمة التوصيات وبتستنى الرد. ولأنه خدمة التوصيات بطيئة جداً، كل طلب لصفحة منتج كان “يحجز” خيط معالجة (Thread) في الخدمة الرئيسية وينتظر… وينتظر… وينتظر. مع آلاف المستخدمين اللي بفوتوا بنفس الوقت، كل خيوط المعالجة في الخدمة الرئيسية انحجزت وانخنقت، وصارت غير قادرة على استقبال أي طلبات جديدة. النظام كله انشلّ بسبب خدمة واحدة، غير حرجة، صارت عنق الزجاجة. هذا يا جماعة هو “تأثير الدومينو” أو الـ “Cascading Failure” بأبشع صوره.

في تلك الليلة، كان الحل المؤقت هو إيقاف خدمة التوصيات يدويًا وإعادة تشغيل كل شيء. خسرنا وقت ثمين ومبيعات كثيرة، لكن تعلمنا درسًا قاسيًا لن ننساه. ومن يومها، صار نمط “قاطع الدائرة” أو الـ Circuit Breaker جزء لا يتجزأ من أي نظام ببنيه.

ما هو تأثير الدومينو (Cascading Failure) في عالم البرمجيات؟

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

تخيلوا قطع الدومينو المصفوفة ورا بعض. لو وقعت قطعة واحدة، رح توقّع اللي وراها، واللي وراها، لحد ما ينهار الصف كله. هذا بالضبط ما يحدث في أنظمتنا:

  1. خدمة (أ) تبطئ أو تفشل: قد يكون السبب ضغطًا عاليًا، خطأ برمجيًا، أو مشكلة في قاعدة البيانات.
  2. خدمة (ب) التي تعتمد على (أ) تبدأ بالانتظار: كل طلب من (ب) إلى (أ) يأخذ وقتًا طويلاً جدًا (Timeout) أو يفشل.
  3. استنزاف موارد خدمة (ب): خيوط المعالجة (Threads)، اتصالات الشبكة (Connections)، والذاكرة في خدمة (ب) تُستهلك بالكامل وهي تنتظر الرد من (أ).
  4. خدمة (ب) تفشل بدورها: تصبح خدمة (ب) غير قادرة على خدمة أي طلبات أخرى، حتى تلك التي لا تعتمد على (أ).
  5. الدومينو يستمر: أي خدمة أخرى تعتمد على (ب) ستعاني من نفس المصير، وهكذا ينتشر الفشل كالنار في الهشيم حتى يشل النظام بأكمله.

المشكلة الأكبر هي أن الفشل في جزء صغير وغير مهم من النظام يمكن أن يؤدي إلى انهيار كامل. وهذا ما يجعل بناء أنظمة مرنة (Resilient Systems) أمرًا حتميًا وليس رفاهية.

الحل السحري: نمط قاطع الدائرة (Circuit Breaker Pattern)

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

في البرمجة، قاطع الدائرة هو كائن (Object) يراقب الاتصالات بين الخدمات. هو عبارة عن “وكيل” (Proxy) يجلس بين الخدمة الطالبة (Caller) والخدمة المطلوبة (Callee) وله ثلاث حالات:

1. الحالة المغلقة (Closed)

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

Circuit Breaker - Closed State

2. الحالة المفتوحة (Open)

هنا “فصل” القاطع. عندما يدخل القاطع هذه الحالة، فإنه يمنع أي طلبات من الوصول إلى الخدمة المطلوبة. بدلاً من محاولة الاتصال وانتظار الفشل، يقوم القاطع بإرجاع خطأ فوري للخدمة الطالبة (Fail Fast). هذا الإجراء له فوائد جبارة:

  • حماية الخدمة الطالبة: بمنعها من إهدار مواردها في انتظار خدمة فاشلة.
  • حماية الخدمة المطلوبة: بإعطائها فرصة “لتتنفس” وتتعافى من الضغط الذي تسبب في فشلها دون إغراقها بطلبات إضافية.

يبقى القاطع في الحالة المفتوحة لمدة زمنية محددة مسبقًا (مثلاً، 30 ثانية). بعد انتهاء هذه المدة، ينتقل القاطع إلى الحالة التالية.

Circuit Breaker - Open State

3. الحالة نصف المفتوحة (Half-Open)

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

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

هذه الآلية تسمح للنظام “بالشفاء الذاتي” (Self-healing) دون أي تدخل يدوي.

Circuit Breaker - Half-Open State

مثال عملي: تطبيق قاطع الدائرة باستخدام مكتبة Polly في C# .NET

الحكي النظري جميل، لكن خلينا نشوف كيف ممكن نطبق هذا الكلام عمليًا. في عالم .NET، تعتبر مكتبة Polly هي المعيار الذهبي لتطبيق سياسات المرونة (Resilience Policies) مثل إعادة المحاولة (Retry) وقاطع الدائرة.

لنفترض أن لدينا خدمة ProductService تستدعي خدمة RecommendationService عبر HTTP.


// في ملف Program.cs أو Startup.cs
// تعريف سياسة قاطع الدائرة
var circuitBreakerPolicy = HttpPolicyExtensions
    .HandleTransientHttpError() // التعامل مع أخطاء HTTP الشائعة (5xx, 408)
    .CircuitBreakerAsync(
        handledEventsAllowedBeforeBreaking: 3, // اسمح بـ 3 محاولات فاشلة قبل فتح الدائرة
        durationOfBreak: TimeSpan.FromSeconds(30), // اترك الدائرة مفتوحة لمدة 30 ثانية
        onBreak: (result, timespan) => 
        {
            Console.WriteLine($"Circuit broken for {timespan.TotalSeconds} seconds due to: {result.Exception?.Message}");
        },
        onReset: () => 
        {
            Console.WriteLine("Circuit is reset. Back to normal.");
        },
        onHalfOpen: () => 
        {
            Console.WriteLine("Circuit is now half-open. Next call is a trial.");
        }
    );

// تسجيل HttpClient مع تطبيق السياسة عليه
builder.Services.AddHttpClient<IRecommendationClient, RecommendationClient>(client =>
{
    client.BaseAddress = new Uri("http://recommendation-service-api");
})
.AddPolicyHandler(circuitBreakerPolicy);

// ... باقي الكود

بهذه البساطة! الآن، أي استدعاء يتم من خلال HttpClient المسجل باسم RecommendationClient سيكون محميًا بسياسة قاطع الدائرة التي عرفناها.

  • إذا فشل الاتصال بخدمة التوصيات 3 مرات متتالية، ستفتح الدائرة.
  • لـ 30 ثانية تالية، أي محاولة للاتصال سترجع خطأ BrokenCircuitException فورًا، دون إرسال طلب حقيقي.
  • بعد 30 ثانية، ستدخل الدائرة في حالة نصف مفتوحة، وتسمح بمرور طلب واحد. إذا نجح، تعود الدائرة مغلقة. إذا فشل، تعود مفتوحة لـ 30 ثانية أخرى.

نصيحة من خبرة أبو عمر: لا تكتفِ بإرجاع خطأ عند فتح الدائرة. قم بتطبيق نمط “الحل البديل” (Fallback). عندما تكون الدائرة مفتوحة، بدلاً من رمي استثناء (Exception)، يمكنك إرجاع قيمة بديلة معقولة.


// يمكن دمج قاطع الدائرة مع سياسة الحل البديل (Fallback)
var fallbackPolicy = Policy<HttpResponseMessage>
    .Handle<BrokenCircuitException>()
    .FallbackAsync(
        new HttpResponseMessage(HttpStatusCode.OK)
        {
            // ارجع قائمة فارغة أو قائمة منتجات عامة "الأكثر مبيعًا"
            Content = new StringContent("[]", Encoding.UTF8, "application/json") 
        }, 
        onFallback: (response, context) =>
        {
            Console.WriteLine("Circuit is open. Returning fallback response.");
            return Task.CompletedTask;
        });

// دمج السياستين معًا (يتم تنفيذ الحل البديل حول قاطع الدائرة)
var resilientPolicy = Policy.WrapAsync(fallbackPolicy, circuitBreakerPolicy);

// ... تسجيل الـ HttpClient مع السياسة المدمجة
builder.Services.AddHttpClient<...>().AddPolicyHandler(resilientPolicy);

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

نصائح عملية لتطبيق قاطع الدائرة بفعالية

  1. لا تطبقه على كل شيء: استخدم قاطع الدائرة بشكل استراتيجي على الاتصالات الخارجية (External Calls) التي قد تكون غير مستقرة أو بطيئة، خاصة تلك التي ليست حيوية 100% لعملية معينة.
  2. اضبط الإعدادات بعناية: عدد المحاولات قبل الفتح ومدة الفتح هي أرقام حساسة. إذا كانت المدة قصيرة جدًا، قد لا تعطي الخدمة الفاشلة وقتًا كافيًا للتعافي. إذا كانت طويلة جدًا، ستعطل الميزة لفترة أطول من اللازم. ابدأ بقيم معقولة (مثل 3-5 محاولات ومدة 30-60 ثانية) وراقب أداء النظام وعدّل بناءً على البيانات.
  3. المراقبة والتنبيه: يجب أن تكون حالة قواطع الدائرة جزءًا أساسيًا من لوحات المراقبة (Dashboards). إذا رأيت قاطعًا معينًا “يفتح” بشكل متكرر، فهذه إشارة واضحة على وجود مشكلة مزمنة في الخدمة التي يحميها ويجب التحقيق فيها.
  4. قاطع دائرة لكل مثيل خدمة (Per-Instance) أم مشترك (Shared)؟: إذا كانت خدمتك تتصل بعدة مثيلات (Instances) من خدمة أخرى خلف موازن أحمال (Load Balancer)، فكر في استراتيجيتك. هل تريد قاطع دائرة واحدًا للجميع، أم قاطعًا لكل مثيل على حدة؟ لكل منها مزاياها وعيوبها حسب الموقف.

الخلاصة: ابنِ أنظمة تتوقع الفشل 👍

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

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

تذكروا دائمًا هذه المقولة: “ما تخاف من الفشل، خاف من النظام اللي ما بتحمل الفشل”. تبني أدوات مثل قاطع الدائرة هو خطوتك الأولى نحو بناء أنظمة قوية ومرنة وقادرة على مواجهة عواصف الإنترنت الحقيقية. يلا، شدّوا حيلكم!

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

التكنلوجيا المالية Fintech

كنا نخزن بطاقات الائتمان مباشرة… قصة تسريب بيانات وكيف أنقذني الترميز (Tokenization)

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

15 مارس، 2026 قراءة المزيد
أتمتة العمليات

استيقظتُ في الثالثة فجراً لإعادة تشغيل سيرفر: كيف علّمتُ نظامي أن يشفي نفسه بنفسه عبر الأتمتة؟

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

14 مارس، 2026 قراءة المزيد
تسويق رقمي

إعلاناتي كانت تستهدف الجميع… وبالتالي لم تصل لأحد: كيف استخدمتُ نماذج التجزئة (Clustering) لاكتشاف شرائح عملاء لم أكن أعرف بوجودها؟

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

13 مارس، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

قاعدة بياناتي كانت تتوسل للرحمة: كيف أنقذتني استراتيجية التخزين المؤقت (Caching) من الانهيار؟

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

11 مارس، 2026 قراءة المزيد
البودكاست