عطل خدمة واحدة كاد ينسف النظام: كيف أنقذنا نمط “قاطع الدائرة” من جحيم الأعطال المتتالية؟

يا جماعة الخير، السلام عليكم ورحمة الله.

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

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

بعد ساعات من البحث والتحليل تحت ضغط رهيب، لزقنا بالمكتب للساعة 2 بالليل، اكتشف زميل إلنا ملاحظة دقيقة: كل الأخطاء كانت بتبلش من عند “خدمة الطلبات” (Orders Service). بس الغريب إنه الخدمة نفسها ما عليها ضغط، ومواردها مستقرة. بعد ما حفرنا أعمق، لقينا المصيبة. خدمة الطلبات كانت بتحاول تتصل بـ “خدمة الدفع” (Payment Service) التابعة لجهة خارجية، وهالخدمة كانت بطيئة جداً وأحياناً ما بترد بالمرة. اللي كان يصير إنه كل طلب جديد في خدمة الطلبات كان يفتح “ثريد” (thread) جديد عشان يستنى رد خدمة الدفع. ومع كثرة الطلبات، كل الـ “ثريدات” المتاحة انحجزت وهي بتستنى رد ما رح يجي، والنظام كله “جَمّد” ومات إكلينيكياً. عطل بسيط في خدمة خارجية صغيرة كان زي الفيروس اللي شلّ نظامنا بالكامل. كان يوم أسود من قرن الخروب.

هنا كانت الصرخة: “لازم نلاقي حل لهالأعطال المتتالية (Cascading Failures)!”. ومن رحم هالمعاناة، ولد القرار بتبني نمط تصميمي غيّر طريقة تفكيرنا في بناء الأنظمة المرنة: نمط قاطع الدائرة أو الـ Circuit Breaker.

ما هي مشكلة الأعطال المتتالية (Cascading Failures)؟

قبل ما ندخل في تفاصيل الحل، خلينا نفهم المشكلة بعمق أكبر. في عالم الخدمات المصغرة، كل خدمة بتعتمد على خدمات تانية عشان تأدي وظيفتها. تخيل الشبكة التالية:

  • خدمة الواجهة الأمامية (Frontend Service) تطلب بيانات من خدمة المستخدمين (Users Service).
  • خدمة المستخدمين بدورها تطلب بيانات الطلبات من خدمة الطلبات (Orders Service).
  • خدمة الطلبات تطلب تفاصيل الدفع من خدمة الدفع (Payment Service).

هذا يسمى “سلسلة الاستدعاءات” (Call Chain). الآن، تخيل لو خدمة الدفع (آخر حلقة في السلسلة) توقفت عن العمل أو أصبحت بطيئة جداً. ماذا سيحدث؟

  1. خدمة الطلبات سترسل طلبات إلى خدمة الدفع وتنتظر الرد. وبما أن الرد لا يأتي، ستبدأ مواردها (مثل اتصالات الشبكة، الذاكرة، الـ threads) بالنفاد.
  2. بعد فترة قصيرة، ستصبح خدمة الطلبات نفسها غير قادرة على استقبال أي طلبات جديدة لأن كل مواردها محجوزة.
  3. الآن، خدمة المستخدمين التي تعتمد على خدمة الطلبات ستبدأ بمواجهة نفس المشكلة. سترسل طلبات لخدمة الطلبات التي لا ترد، وستنفد مواردها هي الأخرى.
  4. وهكذا، ينتقل الفشل مثل الدومينو عبر السلسلة حتى يصل إلى الواجهة الأمامية، وفي النهاية ينهار النظام بأكمله.

هذه هي بالضبط الأعطال المتتالية. المشكلة ليست فقط في الخدمة التي فشلت، بل في كيفية تعامل الخدمات الأخرى مع هذا الفشل. الاستمرار في إرسال الطلبات إلى خدمة “ميتة” هو مضيعة للموارد وسبب رئيسي في انهيار النظام.

“في الأنظمة الموزعة، لا تفترض أن الخدمات التي تعتمد عليها ستكون متاحة دائمًا. بل افترض أنها ستفشل، وقم ببناء نظامك على هذا الأساس.”

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

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

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

حالات قاطع الدائرة الثلاث

يعمل قاطع الدائرة في ثلاث حالات رئيسية:

  1. مغلق (Closed): هذا هو الوضع الطبيعي. يتم توجيه جميع الطلبات إلى الخدمة الخارجية. يقوم القاطع بحساب عدد حالات الفشل. إذا سارت الأمور على ما يرام، يبقى القاطع في هذه الحالة. أما إذا تجاوز عدد حالات الفشل في فترة زمنية معينة حدًا معينًا (مثلاً، 50% من آخر 20 طلبًا فشلت)، ينتقل القاطع إلى حالة “مفتوح”.
  2. مفتوح (Open): في هذه الحالة، “فصل” القاطع. أي طلب جديد يتم رفضه فورًا دون محاولة الاتصال بالخدمة الخارجية. يتم إرجاع خطأ فوري للخدمة الطالبة. هذا يحمي نظامك من انتظار خدمة لا تستجيب. بعد فترة زمنية محددة (Cooldown period)، ينتقل القاطع إلى حالة “نصف مفتوح”.
  3. نصف مفتوح (Half-Open): هذه هي مرحلة الاختبار. يسمح القاطع بمرور طلب واحد فقط (أو عدد قليل جدًا) إلى الخدمة الخارجية.
    • إذا نجح هذا الطلب، يفترض القاطع أن الخدمة قد تعافت، وينتقل إلى حالة “مغلق” لتعود الأمور إلى طبيعتها.
    • إذا فشل هذا الطلب، يفترض القاطع أن المشكلة لا تزال قائمة، ويعود إلى حالة “مفتوح” ليبدأ فترة انتظار جديدة.

هذه الآلية الذكية تمنح النظام مرونة وقدرة على الشفاء الذاتي (Self-healing). بدلاً من الانهيار، يقوم النظام بعزل الجزء الفاشل وحماية نفسه.

مثال عملي بالكود (C# مع مكتبة Polly)

من أهم النصائح اللي بقدر أقدمها: لا تخترع العجلة من جديد! هناك مكتبات رائعة ومجربة تطبق هذا النمط وغيره من أنماط المرونة. في عالم الدوت نت (.NET)، تعتبر مكتبة Polly هي المعيار الذهبي.

لنفترض أن لدينا الكود التالي الذي يستدعي خدمة دفع خارجية:


// هذا الكود بدون قاطع دائرة - عرضة للفشل المتتالي
public async Task<PaymentResponse> ProcessPayment(PaymentRequest request)
{
    // httpClient يستدعي خدمة الدفع الخارجية
    var response = await _httpClient.PostAsJsonAsync("api/payments", request);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadFromJsonAsync<PaymentResponse>();
}

الآن، لنضف قاطع دائرة باستخدام Polly. أولاً، نقوم بتعريف “سياسة” (Policy) قاطع الدائرة عند إعداد خدماتنا (في ملف Program.cs أو Startup.cs):


// تعريف سياسة قاطع الدائرة
var circuitBreakerPolicy = HttpPolicyExtensions
    .HandleTransientHttpError() // التعامل مع أخطاء الشبكة الشائعة
    .CircuitBreakerAsync(
        failureThreshold: 0.5, // افتح الدائرة إذا فشل 50% من الطلبات
        samplingDuration: TimeSpan.FromSeconds(60), // خلال آخر 60 ثانية
        minimumThroughput: 20, // يجب أن يكون هناك 20 طلبًا على الأقل في فترة العينة
        durationOfBreak: TimeSpan.FromSeconds(30) // اترك الدائرة مفتوحة لمدة 30 ثانية
    );

// ربط السياسة مع HttpClient الخاص بخدمة الدفع
builder.Services.AddHttpClient<IPaymentService, PaymentService>(client =>
{
    client.BaseAddress = new Uri("https://api.paymentservice.com/");
})
.AddPolicyHandler(circuitBreakerPolicy);

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

شرح الإعدادات:

  • failureThreshold: 0.5: إذا فشلت 50% من الطلبات، تفتح الدائرة.
  • samplingDuration: TimeSpan.FromSeconds(60): يتم حساب نسبة الفشل بناءً على الطلبات في آخر 60 ثانية.
  • minimumThroughput: 20: لن تفتح الدائرة إلا إذا تم إجراء 20 طلبًا على الأقل خلال فترة العينة. هذا يمنع فتح الدائرة بسبب فشل عشوائي لطلب واحد أو اثنين.
  • durationOfBreak: TimeSpan.FromSeconds(30): عندما تفتح الدائرة، ستبقى مفتوحة لمدة 30 ثانية قبل الانتقال إلى حالة “نصف مفتوح”.

بالطبع، هذه الأرقام يجب تعديلها لتناسب طبيعة نظامك وحجم الضغط عليه.

نصائح “أبو عمر” العملية

من خلال تجربتي في تطبيق هذا النمط في مشاريع مختلفة، جمعت لكم هالنصائح اللي من القلب:

  1. ابدأ بسيطًا واستخدم مكتبة جاهزة: مرة أخرى، لا تضيع وقتك في بناء قاطع دائرة من الصفر. مكتبات مثل Polly في C#، أو Resilience4j في Java، أو Hystrix (ولو أنه لم يعد يُطوّر بنشاط) تقدم حلولاً قوية ومختبرة.
  2. لا تكتفِ بقاطع الدائرة فقط: قاطع الدائرة يعمل بشكل أفضل مع أنماط أخرى مثل “إعادة المحاولة” (Retry). يمكنك تكوين سياسة لإعادة المحاولة 2-3 مرات مع فترة انتظار قصيرة بينها (Exponential Backoff)، وإذا فشلت كل المحاولات، عندها يتدخل قاطع الدائرة.
  3. جهز خطة بديلة (Fallback): طيب، قاطع الدائرة فتح، شو نعمل للمستخدم؟ هل نظهر له رسالة خطأ “قبيحة”؟ الأفضل هو توفير سلوك بديل. مثلاً، إذا فشلت خدمة توصيات المنتجات، يمكن عرض قائمة بالمنتجات الأكثر مبيعًا من الذاكرة المؤقتة (Cache). هذا يحسن تجربة المستخدم بشكل كبير.
  4. المراقبة والتنبيهات أهم من أي شيء: لازم تعرف متى وكيف تفتح قواطع الدائرة في نظامك. قم بإضافة سجلات (Logs) عند كل تغيير في حالة القاطع (من مغلق إلى مفتوح والعكس). قم بإعداد تنبيهات (Alerts) لفريقك عندما يفتح قاطع دائرة لخدمة حرجة. هذا يعطيك رؤية فورية عن صحة نظامك.
  5. اضبط إعداداتك بحكمة: الأرقام التي وضعتها في مثال الكود ليست مقدسة. يجب أن تعكس حساسية الخدمة. خدمة الدفع الحرجة قد تحتاج إلى قاطع دائرة أكثر حساسية من خدمة إرسال الإيميلات الترحيبية مثلاً.

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

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

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

يعطيكم ألف عافية.

أبو عمر

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

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

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

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

آخر المدونات

خوارزميات

قاعدة بياناتنا كانت تجيب على ‘هل هذا موجود؟’ ببطء قاتل: كيف أنقذنا ‘مرشح بلوم’ (Bloom Filter) من جحيم الاستعلامات المكلفة؟

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

14 أبريل، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

موقعنا كان يستبعد الملايين بصمت: كيف أنقذتنا ‘معايير الوصولية’ (Accessibility) من جحيم التصميم الإقصائي؟

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

14 أبريل، 2026 قراءة المزيد
برمجة وقواعد بيانات

استعلاماتنا كانت تزحف: كيف أنقذتنا ‘فهارس قاعدة البيانات’ من جحيم فحص الجداول الكاملة؟

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

14 أبريل، 2026 قراءة المزيد
الشبكات والـ APIs

أنظمتنا كانت تسأل ‘هل هناك جديد؟’ كل ثانية: كيف أنقذتنا ‘الخطافات الشبكية’ (Webhooks) من جحيم الاستقصاء المستمر (Polling)؟

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

14 أبريل، 2026 قراءة المزيد
الحوسبة السحابية

تطبيقنا كان رهينة منطقة جغرافية واحدة: كيف أنقذتنا استراتيجية ‘متعددة المناطق’ (Multi-Region) من جحيم الانقطاع الكامل؟

أشارككم قصة حقيقية عن يوم توقف فيه تطبيقنا بالكامل بسبب انقطاع في منطقة سحابية واحدة، وكيف كانت استراتيجية "متعددة المناطق" (Multi-Region) هي طوق النجاة. سأشرح...

14 أبريل، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

حسابي في GitHub كان مقبرة صامتة: كيف أنقذني ‘ملف التعريف المميّز’ من جحيم التجاهل؟

كنتُ أرى حسابي في GitHub كمقبرة لمشاريعي، مجرد أرشيف لا يلتفت إليه أحد. في هذه المقالة، سأشارككم قصتي، يا جماعة، كيف حوّلت هذا المستودع الصامت...

14 أبريل، 2026 قراءة المزيد
البنية التحتية وإدارة السيرفرات

بنيتنا التحتية كانت قصرًا من رمال: كيف أنقذنا Terraform من جحيم التكوين اليدوي والانحراف الصامت؟

أشارككم قصة حقيقية عن ليلة كادت أن تنهار فيها كل أنظمتنا بسبب تغيير يدوي بسيط. سأشرح لكم كيف انتقلنا من فوضى الإعدادات اليدوية إلى عالم...

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