فشل خدمة واحدة كاد يُسقط النظام بأكمله: كيف أنقذنا نمط ‘قاطع الدائرة’ من جحيم الفشل المتتالي؟

يا أهلاً وسهلاً فيكم جميعاً، معكم أخوكم أبو عمر.

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

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

في هذيك اللحظة، عرفت إنه الـ try-catch البسيطة اللي كنا حاطينها ما كانت كافية أبداً. كنا محتاجين آلية أذكى، آلية تحمي النظام من نفسه. كنا محتاجين “قاطع دائرة”.

ما هو الجحيم المتتالي (Cascading Failure) في الخدمات المصغرة؟

في عالم معمارية الخدمات المصغرة (Microservices)، كل خدمة هي كيان مستقل، لكنها في الغالب تعتمد على خدمات أخرى لتؤدي وظيفتها. تخيل السيناريو التالي:

  • خدمة A (واجهة المستخدم) تطلب بيانات من خدمة B (ملف المستخدم).
  • خدمة B بدورها تطلب بيانات من خدمة C (التفضيلات).

الآن، ماذا لو فشلت خدمة C أو أصبحت بطيئة جداً؟

  1. خدمة B سترسل طلباً إلى C وتنتظر الرد. بما أن C لا تستجيب، سيبقى الطلب معلقاً حتى انتهاء مهلة الاتصال (Timeout).
  2. أثناء انتظار خدمة B، ستتراكم الطلبات الجديدة القادمة إليها من خدمة A. كل طلب جديد سيستهلك مورداً (connection, thread) في خدمة B وهو ينتظر خدمة C الميتة.
  3. بعد فترة قصيرة، ستستهلك خدمة B كل مواردها المتاحة في انتظار الردود التي لن تأتي أبداً، وستبدأ هي الأخرى بالفشل أو رفض الطلبات الجديدة.
  4. الآن، خدمة A التي تعتمد على B ستواجه نفس المصير. ستستمر في إرسال الطلبات إلى خدمة B التي أصبحت لا تستجيب، حتى تستهلك كل مواردها وتنهار هي الأخرى.

وهكذا، مثل أحجار الدومينو، فشل خدمة واحدة صغيرة في آخر السلسلة تسبب في انهيار النظام بأكمله. هذا هو بالضبط “الفشل المتتالي”.

البطل المنقذ: نمط قاطع الدائرة (Circuit Breaker)

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

في عالم البرمجيات، يقوم نمط قاطع الدائرة بنفس الدور. إنه وكيل (Proxy) يجلس بين الخدمة التي تطلب (Client) والخدمة المطلوبة (Supplier)، ويراقب حالة الاتصال بينهما. وله ثلاث حالات رئيسية:

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

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

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

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

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

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

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

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

هذه الآلية تمنع عودة الضغط الكامل فجأة على خدمة قد تكون لا تزال هشة وتتعافى ببطء.

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

الكلام النظري جميل، لكن “الحكي ببلاش” زي ما بنقول. خلينا نشوف كيف طبقنا هذا الكلام عملياً. في بيئة الدوت نت (.NET)، تعتبر مكتبة Polly هي المعيار الذهبي لتطبيق أنماط المرونة (Resilience Patterns) مثل قاطع الدائرة.

الكود “قبل” قاطع الدائرة (الطريق إلى الجحيم)

كان الكود الخاص بنا يبدو شيئاً كهذا، بسيط وساذج:


public class RecommendationService
{
    private readonly HttpClient _httpClient;

    public async Task<ProductRecommendation> GetRecommendations(string productId)
    {
        try
        {
            // هذا الاتصال هو الذي كان يسبب المشكلة
            var response = await _httpClient.GetAsync($"/recommendations/{productId}");
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadFromJsonAsync<ProductRecommendation>();
        }
        catch (HttpRequestException ex)
        {
            // كنا نسجل الخطأ فقط، لكننا نستمر في المحاولة مع كل طلب جديد
            _logger.LogError(ex, "Failed to get recommendations for product {productId}", productId);
            throw; 
        }
    }
}

الكود “بعد” قاطع الدائرة (النجاة)

بعد أن تعلمنا الدرس بالطريقة الصعبة، قمنا بدمج Polly. أولاً، في ملف Startup.cs أو Program.cs، قمنا بتعريف “سياسة” قاطع الدائرة عند تسجيل HttpClient:


// تعريف سياسة قاطع الدائرة
var circuitBreakerPolicy = HttpPolicyExtensions
    .HandleTransientHttpError() // التعامل مع أخطاء الشبكة و 5xx
    .CircuitBreakerAsync(
        5, // عدد الأخطاء المتتالية لفتح الدائرة
        TimeSpan.FromSeconds(30), // مدة بقاء الدائرة مفتوحة
        onBreak: (exception, timespan) => 
        {
            // إجراء يتم عند فتح الدائرة (مهم جداً للتسجيل والمراقبة)
            Console.WriteLine($"Circuit broken for {timespan.TotalSeconds} seconds due to: {exception.Message}");
        },
        onReset: () => 
        {
            // إجراء يتم عند إغلاق الدائرة مجدداً
            Console.WriteLine("Circuit reset and is now closed.");
        },
        onHalfOpen: () => 
        {
            // إجراء يتم عند الدخول في حالة نصف مفتوحة
            Console.WriteLine("Circuit is now half-open, next call is a trial.");
        }
    );

// إضافة السياسة إلى HttpClient المستخدم
services.AddHttpClient<RecommendationService>()
        .AddPolicyHandler(circuitBreakerPolicy);

لاحظ كيف أن الكود الخاص بـ RecommendationService نفسه لم يتغير! كل المنطق تم حقنه بطريقة نظيفة ومنظمة. الآن، عندما تفشل خدمة التوصيات 5 مرات متتالية، ستقوم سياسة Polly بفتح الدائرة لمدة 30 ثانية. أي طلب خلال هذه الفترة سيفشل فوراً مع إرجاع BrokenCircuitException، مما يحمي خدمتنا من الانتظار واستهلاك الموارد.

نصائح عملية من خبرة أبو عمر 🧔

تطبيق النمط هو البداية فقط. إليكم بعض الدروس التي تعلمتها والتي لن تجدوها بسهولة في التوثيق الرسمي:

1. لا تكتفِ بالفشل، قدم بديلاً (Fallback)

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

يمكن تطبيق ذلك بسهولة مع Polly أيضاً:


// سياسة للتعامل مع فتح الدائرة
var fallbackPolicy = Policy<HttpResponseMessage>
    .Handle<BrokenCircuitException>()
    .FallbackAsync(
        new HttpResponseMessage(HttpStatusCode.OK)
        {
            // إرجاع قائمة منتجات محفوظة مسبقاً من الكاش
            Content = new StringContent(GetDefaultRecommendationsAsJson()) 
        }
    );

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

services.AddHttpClient<RecommendationService>()
        .AddPolicyHandler(policyWrap);

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

2. اضبط إعداداتك لكل خدمة على حدة

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

3. المراقبة والتنبيهات هي عيونك

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

نصيحة من القلب: قاطع الدائرة ليس مجرد نمط برمجي، بل هو تغيير في العقلية. هو اعتراف بأن الفشل جزء لا مفر منه من الأنظمة الموزعة، وأن مهمتنا ليست منع الفشل، بل احتواؤه والتعافي منه بأناقة. 💪

الخلاصة: ابنِ حصونك قبل المعركة

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

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

أتمنى لكم كل التوفيق في بناء أنظمة قوية ومرنة. وإذا عندكم أي سؤال، أنا جاهز في التعليقات. 🛡️

أبو عمر

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

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

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

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

آخر المدونات

الحوسبة السحابية

مستقبلنا كان مرهونًا بمزود واحد: كيف أنقذتنا استراتيجية السحابة المتعددة (Multi-Cloud) من جحيم الـ Vendor Lock-in؟

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

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

محفظة أعمالي كانت مقبرة لمشاريع الدورات: كيف أنقذني ‘المشروع الرائد’ من جحيم الرفض المتكرر؟

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

16 أبريل، 2026 قراءة المزيد
التكنلوجيا المالية Fintech

بياناتنا المالية كانت حبيسة الصوامع: كيف أنقذتنا واجهات ‘المصرفية المفتوحة’ (Open Banking APIs) من جحيم الأنظمة المغلقة؟

كنا نعيش في جحيم الأنظمة المصرفية المغلقة، حيث بياناتنا المالية سجينة في جزر منعزلة. في هذه المقالة، أروي لكم كيف غيرت واجهات "المصرفية المفتوحة" (Open...

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

بنيتنا التحتية كانت تتغير من وراء ظهورنا: كيف أنقذنا Terraform من جحيم ‘الانحراف التكويني’ (Configuration Drift)؟

أشارككم قصة حقيقية من قلب المعركة التقنية، عندما كانت بنيتنا التحتية تتغير كالكثبان الرملية تحت أقدامنا. اكتشفوا معنا ما هو "الانحراف التكويني" (Configuration Drift)، وكيف...

15 أبريل، 2026 قراءة المزيد
ادارة الفرق والتنمية البشرية

من جحيم الاعتماد على شخص واحد إلى ذاكرة فريق جماعية: قصة نجاحنا مع سجلات قرارات الهندسة (ADRs)

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

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

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

أشارككم قصة حقيقية من قلب الميدان، كيف تحول فريقنا من الإرهاق في المهام المتكررة إلى الإبداع والإنتاجية بفضل أتمتة العمليات الروبوتية (RPA). مقالة عملية للمبرمجين...

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