نمط قاطع الدائرة (Circuit Breaker): الطفاية التي أخمدت حريق الأعطال المتتالية في نظامنا

ليلة لا تُنسى: حين كاد “عرض خاص” أن يُغرق السفينة بأكملها

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

فجأة، حوالي الساعة 12:15، بدأت التنبيهات توصلنا زي المطر. “System is slow”, “504 Gateway Timeout”. فتحت لوحة المراقبة (Dashboard) وشفت الكارثة بعيني: استجابة الخوادم بتزيد بشكل جنوني، واستخدام المعالج (CPU) وصل 100% على كل الخدمات تقريبًا. الموقع صار شبه ميت، والزبائن مش قادرين يفتحوا حتى صفحة المنتج.

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

المشكلة ما كانت في فشل الخدمة نفسها، المصيبة كانت أكبر. الخدمة الرئيسية اللي بتعرض صفحة المنتج كانت تستدعي خدمة الاقتراحات وتضل تستنى الرد. ولما خدمة الاقتراحات صارت “تعلق”، كل طلب لصفحة منتج صار يحجز “thread” أو “process” على الخادم الرئيسي ويضل يستنى… ويستنى… ويستنى. خلال دقائق، كل الـ threads المتاحة على الخادم الرئيسي انحجزت، وصار الخادم مشلول تمامًا، غير قادر على خدمة أي طلب جديد، حتى لو كان طلب بسيط لصفحة “من نحن”.

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

ما هو نمط قاطع الدائرة (Circuit Breaker)؟

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

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

لما الدائرة تفتح، أي طلب جديد لهي الخدمة ما بروح الها أصلاً! قاطع الدائرة بيرفضه فورًا وبيرجع خطأ مباشر وسريع (Fail Fast). وبهيك، بنحمي خدمتنا الرئيسية من إنها تضل تستنى وتستهلك مواردها على خدمة هي أصلاً “ميّتة”.

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

لفهم آلية العمل بشكل أفضل، قاطع الدائرة يمر بثلاث حالات رئيسية:

  • مغلق (Closed): هذا هو الوضع الطبيعي. الطلبات تمر من خلال قاطع الدائرة إلى الخدمة الخارجية بدون أي مشاكل. القاطع في هذه الأثناء يقوم فقط بعدّ الأخطاء بصمت.
  • مفتوح (Open): عند تجاوز عدد الأخطاء لحد معين (مثلاً، 5 أخطاء في آخر 30 ثانية)، ينتقل القاطع إلى حالة “مفتوح”. في هذه الحالة، يتم رفض جميع الطلبات فورًا دون محاولة الاتصال بالخدمة الفاشلة، ويتم إرجاع خطأ أو قيمة افتراضية. هذا يعطي الخدمة الفاشلة فرصة “لتلتقط أنفاسها” وتتعافى دون أن يغمرها سيل من الطلبات.
  • نصف مفتوح (Half-Open): بعد مرور فترة زمنية محددة في الحالة المفتوحة (مثلاً، دقيقة واحدة)، ينتقل القاطع إلى حالة “نصف مفتوح”. في هذا الوضع، يسمح القاطع بمرور طلب واحد فقط (أو عدد قليل جدًا) إلى الخدمة الخارجية كـ “جس نبض”.
    • إذا نجح هذا الطلب، فهذا مؤشر على أن الخدمة قد تعافت. يعود القاطع إلى حالة “مغلق” وتعود الأمور لطبيعتها.
    • إذا فشل هذا الطلب، فهذا يعني أن الخدمة لا تزال تعاني. يعود القاطع إلى حالة “مفتوح” مرة أخرى ويبدأ مؤقت جديد.

لنطبق هذا عمليًا: مثال بالكود

صحيح أن هناك مكتبات جاهزة وقوية جدًا مثل Resilience4j في عالم Java أو Polly في عالم .NET، لكن فهم المبدأ الأساسي مهم جدًا. إليك مثال بسيط جدًا بلغة Python لتوضيح الفكرة (هذا ليس كودًا للإنتاج، بل للتوضيح فقط):


import time

class CircuitBreaker:
    def __init__(self, failure_threshold=5, recovery_timeout=60):
        self.failure_threshold = failure_threshold  # عدد الأخطاء لفتح الدائرة
        self.recovery_timeout = recovery_timeout    # مدة الانتظار قبل محاولة جس النبض (بالثواني)
        self.failures = 0
        self.state = "CLOSED"
        self.last_failure_time = None

    def execute(self, func):
        if self.state == "OPEN":
            # هل حان وقت الانتقال إلى حالة نصف مفتوح؟
            if time.time() - self.last_failure_time > self.recovery_timeout:
                print("⏳ محاولة جس النبض... الانتقال إلى حالة نصف مفتوح.")
                self.state = "HALF_OPEN"
            else:
                # الدائرة مفتوحة، ارفض الطلب فورًا
                raise Exception("Circuit is OPEN. Service is unavailable.")

        try:
            # في الحالتين المغلقة ونصف المفتوحة، نحاول تنفيذ الطلب
            result = func()
            # إذا نجحنا (خصوصًا في حالة نصف مفتوح)، هذا رائع!
            if self.state == "HALF_OPEN":
                self.reset()
            return result
        except Exception as e:
            # حصل خطأ، لنسجله
            self.record_failure()
            raise e

    def record_failure(self):
        self.failures += 1
        self.last_failure_time = time.time()
        print(f"🔴 تسجيل فشل. عدد حالات الفشل: {self.failures}")
        # إذا كنا في حالة نصف مفتوح وفشلنا، ارجع للحالة المفتوحة
        if self.state == "HALF_OPEN":
            self.trip()
        # إذا تجاوزنا حد الفشل في الحالة المغلقة، افتح الدائرة
        elif self.failures >= self.failure_threshold:
            self.trip()

    def trip(self):
        print(f"🚫 تم تجاوز حد الفشل. الدائرة الآن مفتوحة (OPEN) لمدة {self.recovery_timeout} ثانية.")
        self.state = "OPEN"

    def reset(self):
        print("✅ الخدمة استجابت! الدائرة الآن مغلقة (CLOSED).")
        self.failures = 0
        self.state = "CLOSED"
        self.last_failure_time = None

# --- طريقة الاستخدام ---
# def failing_service():
#     # هذا مجرد محاكاة لخدمة تفشل أحيانًا
#     import random
#     if random.random() > 0.5:
#         raise ConnectionError("Failed to connect")
#     return "Success!"
#
# breaker = CircuitBreaker()
#
# for _ in range(20):
#     try:
#         result = breaker.execute(failing_service)
#         print(f"النتيجة: {result}")
#     except Exception as e:
#         print(f"خطأ: {e}")
#     time.sleep(2) # انتظر قليلاً بين الطلبات

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

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

وهنا بعض النصائح العملية من قلب الميدان:

  1. الفشل الرشيق (Graceful Degradation): لا تكتفِ بإرجاع خطأ عندما تكون الدائرة مفتوحة. هذا أفضل من لا شيء، لكن الأفضل منه هو أن يكون لديك “خطة بديلة” أو Fallback. مثلاً، إذا فشلت خدمة الطقس، اعرض طقس الأمس من الكاش. إذا فشلت خدمة الاقتراحات، اعرض المنتجات الأكثر مبيعًا. الهدف هو أن لا يشعر المستخدم النهائي بالمشكلة قدر الإمكان.
  2. المراقبة والتنبيهات هي عيونك: يجب أن تقوم بتسجيل (Logging) كل تغيير في حالة قاطع الدائرة (من مغلق لمفتوح، من مفتوح لنصف مفتوح، إلخ). ويجب أن يكون لديك تنبيهات (Alerts) تصلك فورًا عند فتح الدائرة. “إذا ما بتشوف شو بصير، أنت أعمى يا صاحبي”.
  3. اضبط عياراتك بحكمة: تحديد قيم `failure_threshold` و `recovery_timeout` ليس علمًا دقيقًا 100% بل فن يعتمد على التجربة. قيمة منخفضة جدًا قد تجعل القاطع يفصل بسبب مشكلة عابرة في الشبكة. وقيمة مرتفعة جدًا قد تترك النظام يعاني لفترة طويلة قبل أن يتدخل القاطع. ابدأ بقيم منطقية وراقب أداء النظام وعدّلها حسب الحاجة.
  4. استخدمه لكل شيء قد يفشل: نمط قاطع الدائرة ليس فقط للاتصالات عبر الشبكة (API calls). يمكنك استخدامه لتغليف أي عملية غير مستقرة أو قد تستغرق وقتًا طويلاً: الاتصال بقاعدة البيانات، الوصول لنظام الملفات، أو حتى استدعاء خوارزمية معقدة تستهلك موارد كبيرة.

الخلاصة: لا تنتظر الحريق لتشتري الطفاية 💡

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

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

أبو عمر

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

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

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

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

آخر المدونات

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

كانت تطبيقاتنا المالية جزرًا معزولة: كيف أنقذتنا واجهات Open Banking (PSD2) من جحيم تجربة المستخدم؟

بصفتي مطور برمجيات، عانيت طويلًا من عزلة البيانات المالية في البنوك التقليدية. تروي هذه المقالة كيف حررت واجهات البنوك المفتوحة (Open Banking) البيانات، ومكّنت المطورين...

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

من شكاوى المستخدمين إلى لوحات المراقبة: كيف أنقذنا Prometheus وGrafana من كابوس ‘الموقع لا يعمل’؟

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

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

كانت اجتماعاتنا الفردية مضيعة للوقت: كيف أنقذنا نموذج ‘الموقف-السلوك-التأثير’ (SBI) من جحيم المحادثات السطحية؟

أشارككم تجربتي كقائد فريق تقني، وكيف حوّلنا اجتماعاتنا الفردية (1-on-1s) من لقاءات سطحية ومملة إلى محادثات بنّاءة ومثمرة باستخدام نموذج التغذية الراجعة البسيط والفعّال (الموقف-السلوك-التأثير)....

22 مايو، 2026 قراءة المزيد
​معمارية البرمجيات

ذاكرة الفريق هي التوثيق الوحيد: كيف أنقذتنا ‘سجلات القرارات المعمارية’ (ADRs) من جحيم “لماذا فعلنا ذلك؟”

أشارككم قصة حقيقية عن ضياع المعرفة في فريقنا وكيف تحولت 'سجلات القرارات المعمارية' (ADRs) إلى منقذنا. اكتشفوا كيف يمكن لهذه الأداة البسيطة أن تنهي جحيم...

22 مايو، 2026 قراءة المزيد
ذكاء اصطناعي

نماذجنا اللغوية كانت تهذي: كيف أنقذ “التوليد المعزز بالاسترجاع” (RAG) مشاريعنا من جحيم الهلوسة؟

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

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