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

ليلة لا تُنسى: حينما توقف كل شيء بسبب “التوصيات”

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

من بين هاي الخدمات، كانت في خدمة “ذكية” شوي، خدمة التوصيات (Recommendation Service). هاي الخدمة بتستخدم خوارزميات تعلم آلة معقدة عشان تقترح منتجات للمستخدم بناءً على سلوكه. كانت هي “الجوهرة” في نظامنا، والكل فخور فيها.

في ليلة خميس، حوالي الساعة 10 بالليل، بدأت التنبيهات توصل على تلفوني زي المطر. “System Unresponsive”، “High Latency”، “503 Service Unavailable”. فتحت اللابتوب بسرعة وأنا مش فاهم شو اللي بصير. الموقع واقع! كل الصفحات بتحمّل للأبد وما بتفتح. فريق الدعم الفني بحكي إنه العملاء بشتكوا والضغط كبير.

دخلنا في دوامة… كل الفرق بتفحص خدماتها، والكل بحكي “خدمتي شغالة تمام!”. لكن النظام ككل كان في حالة شلل تام. بعد ساعة من البحث والتحليل تحت ضغط رهيب، اكتشفنا المصيبة. خدمة التوصيات، جوهرتنا، كانت بتعاني من مشكلة في قاعدة البيانات تبعتها، وصارت بطيئة جداً في الرد، وأحياناً ما بترد بالمرة (Timeout).

المشكلة الحقيقية ما كانت في خدمة التوصيات بحد ذاتها، بل في كيفية تعامل الخدمات الأخرى معها. خدمة عرض المنتجات (Product Page Service) كانت بتستنى رد من خدمة التوصيات عشان تعرضها للمستخدم. ولما خدمة التوصيات صارت بطيئة، صارت خدمة عرض المنتجات “معلّقة” وهي بتستنى. ومع آلاف الطلبات في الدقيقة، كل الموارد المتاحة لخدمة عرض المنتجات (threads) استُنفذت وهي بتستنى رد ما رح يوصل. وبالتالي، خدمة عرض المنتجات نفسها صارت غير متاحة، وهذا الأثر انتقل زي الدومينو لباقي أجزاء النظام. عطل واحد صغير في خدمة “غير أساسية” أسقط النظام بأكمله. هاي الظاهرة يا جماعة اسمها “الأعطال المتتالية” أو Cascading Failures.

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

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

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

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

لما تكون الدائرة مفتوحة، أي محاولة جديدة لاستدعاء الخدمة الفاشلة رح تفشل فوراً وبدون حتى ما نحاول نتصل فيها. القاطع برجع خطأ فوري مثل “Service unavailable, please try again later”. هذا الإجراء السريع بحقق هدفين رئيسيين:

  1. حماية نظامنا (The Caller): بمنع استنزاف موارده (مثل الـ threads والـ connections) في انتظار خدمة لا تستجيب. هذا بخليه يضل شغال ويخدم باقي الطلبات اللي ما بتعتمد على الخدمة الفاشلة.
  2. إعطاء الخدمة الفاشلة فرصة للتعافي (The Callee): لما نوقف سيل الطلبات اللي رايح عليها، بنعطيها مساحة “تتنفس” وتتعافى من المشكلة اللي بتعاني منها (مثلاً، إعادة تشغيل، تخفيف الحمل، إلخ).

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

عشان نفهم كيف بيشتغل بالتفصيل، لازم نعرف إنه قاطع الدائرة بمر بثلاث حالات رئيسية:

1. الدائرة مغلقة (Closed)

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

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

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

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

بعد انتهاء فترة الانتظار في الحالة المفتوحة، القاطع بدخل في حالة “نصف مفتوحة”. هاي حالة اختبار حساسة. القاطع بسمح لطلب واحد فقط بالمرور للخدمة المستهدفة. مصير هذا الطلب هو اللي بحدد شو رح يصير بعدين:

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

هذا التناوب بين الحالات الثلاث هو اللي بعطي النمط قوته ومرونته.

مثال عملي بالكود (بايثون كمثال)

خلينا نشوف كيف ممكن نطبق هذا المفهوم بشكل بسيط باستخدام لغة بايثون. رح نعمل كلاس بسيط اسمه CircuitBreaker.


import time
from enum import Enum

class State(Enum):
    CLOSED = 1
    OPEN = 2
    HALF_OPEN = 3

class CircuitBreaker:
    def __init__(self, failure_threshold=5, recovery_timeout=30):
        self.failure_threshold = failure_threshold  # كم مرة مسموح تفشل قبل ما نفتح الدائرة
        self.recovery_timeout = recovery_timeout    # كم ثانية ننتظر قبل محاولة جديدة
        self.state = State.CLOSED
        self.failure_count = 0
        self.last_failure_time = None

    def execute(self, func):
        if self.state == State.OPEN:
            # إذا الدائرة مفتوحة، تحقق إذا مر وقت كافي للانتقال لـ Half-Open
            if time.time() - self.last_failure_time > self.recovery_timeout:
                self.state = State.HALF_OPEN
            else:
                # إذا الوقت ما مر، ارفض الطلب فورًا
                raise Exception("Circuit is open. Service is unavailable.")

        if self.state == State.HALF_OPEN:
            # في حالة الاختبار، اسمح بتمرير طلب واحد
            try:
                result = func()
                # نجح! ارجع للوضع الطبيعي
                self.reset()
                return result
            except Exception as e:
                # فشل مرة أخرى، ارجع افتح الدائرة وانتظر من جديد
                self.trip()
                raise e

        # في الحالة الطبيعية (Closed)
        try:
            result = func()
            # إذا نجح، ممتاز
            return result
        except Exception as e:
            # إذا فشل، عدّ الفشل
            self.failure_count += 1
            if self.failure_count >= self.failure_threshold:
                self.trip()
            raise e

    def reset(self):
        # إعادة تعيين للحالة الطبيعية
        self.state = State.CLOSED
        self.failure_count = 0
        self.last_failure_time = None
        print("Circuit has been closed.")

    def trip(self):
        # فتح الدائرة
        self.state = State.OPEN
        self.last_failure_time = time.time()
        print("Circuit has been opened.")

# --- كيفية الاستخدام ---

# خدمة وهمية ممكن تفشل
def potentially_failing_service():
    import random
    if random.random() < 0.8: # 80% احتمال الفشل
        print("Service call failed!")
        raise IOError("Service is down!")
    print("Service call succeeded!")
    return "Success"

# إنشاء قاطع دائرة
breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=10)

# محاكاة 15 طلب
for i in range(15):
    print(f"n--- Request {i+1} ---")
    try:
        breaker.execute(potentially_failing_service)
    except Exception as e:
        print(f"Caught exception: {e}")
    time.sleep(1)

لو شغّلت هذا الكود، رح تلاحظ إنه بعد 3 محاولات فاشلة متتالية، القاطع رح يفتح الدائرة (Circuit has been opened). بعدها، لمدة 10 ثواني، أي محاولة رح تفشل فورًا مع رسالة “Circuit is open”. بعد 10 ثواني، رح يحاول مرة واحدة (Half-Open)، وإذا نجحت، رح يرجع للوضع الطبيعي (Closed).

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

تطبيق النمط مش مجرد كتابة كود، في شوية نقاط عملية لازم تنتبهوا إلها:

  • الـ Fallback هو صديقك: لما قاطع الدائرة يفتح، شو لازم يصير؟ بدل ما ترجع خطأ للمستخدم، ممكن ترجع بيانات بديلة (Fallback). مثلاً، في قصة خدمة التوصيات، كان ممكن نعرض “أكثر المنتجات مبيعًا” من نسخة مخبأة (cache) بدل ما نترك المكان فاضي أو نرجع خطأ.
  • الضبط والمعايرة (Configuration): الأرقام السحرية (failure_threshold و recovery_timeout) مهمة جدًا. إذا كان الـ threshold قليل جدًا، القاطع رح يفتح مع أي مشكلة عابرة. وإذا كان عالي جدًا، ما رح يحمي نظامك بالوقت المناسب. لازم تختار هاي القيم بناءً على طبيعة خدمتك وأهميتها.
  • لا تخترع العجلة: بدل ما تبني قاطع الدائرة من الصفر في كل مرة، استخدم مكتبات جاهزة ومجربة. في عالم Java و Spring، عندك مكتبة Resilience4j. في عالم .NET، عندك Polly. وفي بايثون، عندك pybreaker. هاي المكتبات بتوفر ميزات متقدمة ومراقبة وإدارة سهلة.
  • المراقبة والتنبيه (Monitoring & Alerting): لازم يكون عندك لوحة مراقبة (Dashboard) بتورجيك حالة قواطع الدائرة في نظامك. بدك تعرف متى بتفتح الدوائر، وكم مرة، ولأي خدمات. هذا بعطيك مؤشر مبكر جدًا عن وجود مشاكل في النظام.

الخلاصة: من الفوضى إلى المرونة

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

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

فكر فيه كأنه “تأمين” على خدماتك. يمكن ما تحتاجه كل يوم، لكن في اليوم اللي رح تحتاجه فيه، رح تكون ممتن جدًا لوجوده. 👍

أبو عمر

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

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

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

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

آخر المدونات

التوظيف وبناء الهوية التقنية

مقابلاتي لتصميم النظم كانت كارثة: كيف أنقذني هذا الإطار المنهجي من جحيم الرفض؟

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

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

شبكة خدماتي المصغرة كانت الغرب المتوحش: كيف أنقذتني ‘شبكة الخدمات’ (Service Mesh) من جحيم انعدام الأمان والتحكم؟

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

4 أبريل، 2026 قراءة المزيد
اختبارات الاداء والجودة

اختباراتي كانت تمر بنجاح لكن تطبيقي ينهار: كيف أنقذني “الاختبار الطفري” من جحيم الثقة الزائفة؟

أشارككم قصة حقيقية حول كيف خدعتني نسبة تغطية الاختبارات 100%، وكيف اكتشفت أن جودة اختباراتي كانت ضعيفة. سنتعمق في مفهوم "الاختبار الطفري" (Mutation Testing) كحل...

4 أبريل، 2026 قراءة المزيد
نصائح برمجية

شيفرتي كانت حقل ألغام: كيف قضيت على ‘الأرقام السحرية’ الغامضة باستخدام الثوابت (Constants) والتعدادات (Enums)؟

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

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