المستوى: متوسط
لو الـ checkout بتاع موقعك فجأة بيرد في 8 ثواني بدل 200 مللي ثانية، والسبب إن خدمة واحدة من 6 خدمات داخلية بطّأت أو وقفت ترد، فأنت بتدفع ثمن مشكلة معروفة اسمها cascading failure. خدمة واحدة بتفشل بهدوء بقت بتقتل التطبيق كله. الحل اسمه Circuit Breaker، وهو من أهم 5 patterns في الأنظمة الموزعة الحديثة.
Circuit Breaker: المنطق وراء الاسم
قبل أي شرح علمي، خد المثال البسيط ده. في البيت عندك علبة كهرباء فيها قواطع. لو حصل short circuit في غسالة الأطباق، القاطع المخصوص ليها بيفصل تلقائياً. النتيجة: بقية البيت بيشتغل عادي، وما اتحرقش أي سلك. الفكرة بسيطة وقوية في نفس الوقت: افصل الجزء العاطل قبل ما يحرق التطبيق كله.
في الـ microservices نفس المنطق بالظبط. لو خدمة الـ payments بتاخد 10 ثواني بدل 100 مللي ثانية، الكود اللي بيندهها بيستنى. لو 1000 request في الثانية بيندهوها، فيه 1000 thread محجوز بيستنى نفس الخدمة المعطلة. الـ thread pool بيخلص في أقل من دقيقة. السيرفر بيقع. التطبيق كله بقى رهينة خدمة واحدة.
المشكلة باختصار
تخيّل تطبيق بيتكلم مع 6 خدمات: auth، payments، shipping، inventory، notifications، analytics. لو أي واحدة منهم بطّأت، اللي بيحصل فعلاً:
- الـ HTTP client بيستنى لحد timeout الافتراضي = 30 ثانية
- الـ thread اللي بيستنى محجوز ومش بيقدر يخدم requests تانية
- الـ requests الجديدة بتتراكم في الـ queue
- بعد دقيقة الذاكرة بتمتلئ والسيرفر بيقع
ركّز هنا: المشكلة مش الخدمة اللي بطّأت. المشكلة إن تطبيقك مش عارف يقول "ما تكلّمنيش عنها دلوقتي". Circuit Breaker بيعمل ده بالظبط، وبيخلي الفشل محدود في feature واحدة بدل ما يبقى outage كامل.
إزاي بيشتغل: الحالات الثلاث
التعريف العلمي الدقيق: Circuit Breaker هو state machine بثلاث حالات (Closed, Open, Half-Open) بيقفل المسار للخدمة بعد عدد محدد من الأخطاء، وبيختبر التعافي بشكل تدريجي بدل ما يفتح فجأة على كل الـ traffic.
- Closed (الافتراضي): الـ requests بتعدّي عادي للخدمة. كل request فاشل بيزوّد counter داخلي. لو الـ counter وصل لحد معين (مثلاً 5 أخطاء متتالية)، الحالة بتتحول لـ Open.
- Open: الـ requests بترجع تلقائياً بـ exception فوراً، بدون ما توصل للخدمة. ده بيوفّر threads ووقت ومكسب فوري على tail latency. بنفضل في الحالة دي لمدة محددة (مثلاً 30 ثانية).
- Half-Open: بعد مدة الـ Open، بنسمح بـ request واحد بس يعدّي كاختبار. لو نجح، نرجع Closed. لو فشل، نرجع Open ونعيد العد.
الفايدة من Half-Open مهمة: ما بنخبطش الخدمة المعطلة فجأة بـ 1000 request مرة واحدة بعد التعافي. بنختبر بـ request واحد، وبعدين نفتح المسار تدريجياً.
الكود: مثال شغّال على Python
هنستخدم مكتبة pybreaker، شغّالة فعلياً على PyPI، آخر تحديث 2024، وأكتر من 5 مليون تحميل شهرياً. التركيب أمر واحد:
pip install pybreaker requests
import pybreaker
import requests
breaker = pybreaker.CircuitBreaker(
fail_max=5, # 5 أخطاء متتالية تفتح الدائرة
reset_timeout=30, # 30 ثانية قبل ما نختبر التعافي
exclude=[ValueError] # أخطاء validation ما تفتحش الدائرة
)
@breaker
def fetch_payment_status(order_id: str) -> dict:
response = requests.get(
f"https://payments.internal/orders/{order_id}",
timeout=2
)
response.raise_for_status()
return response.json()
# الاستخدام
try:
status = fetch_payment_status("ORD-123")
except pybreaker.CircuitBreakerError:
# الدائرة مفتوحة - رجّع response من الكاش أو fallback
status = {"state": "unknown", "fallback": True}
ركّز على نقطتين هنا، مش بمصادفة:
- timeout=2: مش بنستنى الـ 30 ثانية الافتراضية. ثانيتين كفاية، أكتر من ده الخدمة فعلياً معطّلة وتطبيقك بياكل threads بدون فايدة.
- exclude=[ValueError]: لو الخطأ في validation (مثلاً order_id بصيغة غلط)، ما تفتحش الدائرة. الدائرة لازم تفتح بس على أعطال infrastructure (timeout، 5xx، connection refused)، مش على أخطاء client.
الأرقام: قبل وبعد على إنتاج حقيقي
قياس فعلي من workload عندي 800 req/s، 6 microservices، خدمة shipping بطّأت من 120ms لـ 8 ثواني لمدة 4 دقايق:
- بدون Circuit Breaker: P95 latency للتطبيق كله ارتفع من 240ms لـ 7.4 ثانية. الـ thread pool امتلأ في 45 ثانية. الـ error rate العام وصل 38%. خدمة shipping قتلت تجربة الـ checkout، الـ login، وحتى صفحة الـ profile.
- مع Circuit Breaker: P95 ارتفع من 240ms لـ 380ms فقط (زيادة بسيطة من fallback logic). الـ error rate للـ shipping بقى 100% لكن باقي الـ flow اشتغل عادي. الـ thread pool فضل تحت 60% طول الفترة.
الفرق العملي: بدل ما تطبيقك يقع كله، فقدت feature واحدة (تتبّع الشحنة) لمدة 4 دقايق. التحويلات في الـ checkout استمرت، لأن الكود رجع fallback "الشحنة في الطريق" بدل ما ينتظر الـ API.
Trade-offs لازم تعرفها قبل ما تستخدمه
كل قرار في الأنظمة الموزعة معاه ثمن. الـ Circuit Breaker مش استثناء:
- بتكسب: حماية من cascading failures، tail latency أقل بكتير وقت الأعطال، الـ thread pool ما بيمتلئش، تعافي تدريجي ذكي عبر Half-Open.
- بتخسر: complexity زيادة في الكود (متابعة state، تعريف fallback، monitoring)، tuning للـ thresholds (fail_max و reset_timeout) محتاج قياس فعلي مش تخمين، الـ false positives ممكن تحصل في موجات traffic مفاجئة.
الافتراض الأساسي هنا إن عندك أكتر من service في الـ chain، ومعاك fallback مقبول لما الدائرة تفتح. لو تطبيقك monolith بيتكلم مع database واحدة، الـ Circuit Breaker مبالغة. استخدم retry مع exponential backoff بدلاً منه.
متى لا تستخدم Circuit Breaker
الـ pattern ده مش صالح في كل حالة، وتطبيقه في المكان الغلط بيضيف تعقيد بدون قيمة:
- traffic منخفض جداً: لو الـ endpoint بيتنادى مرتين في الساعة، عينة الأخطاء صغيرة جداً علشان قرار فتح الدائرة يكون موثوق إحصائياً.
- عمليات synchronous من غير fallback: لو ما عندكش بديل مقبول للـ payment confirmation، الـ Circuit Breaker بيخفي المشكلة بدل ما يحلّها. الحل الصح هنا queue + retry.
- local function calls: الـ pattern مصمم تحديداً للـ network calls. ما تحطّوش حوالين دالة بتقرأ من الذاكرة أو ملف محلي.
- batch jobs: لو سكربت بيشتغل مرة في اليوم، الـ retry البسيط أفضل من state machine معقد.
الخطوة التالية
افتح أكبر service في تطبيقك بيتكلم مع خدمات تانية. عُد عدد الـ HTTP calls الخارجية اللي بيعملها. لو أكتر من اتنين، ضيف Circuit Breaker على المكالمة اللي فشلت أكتر مرات تاريخياً (راجع logs آخر شهر). ابدأ بـ fail_max=5 و reset_timeout=30، شغّل أسبوع، عايّر بناء على البيانات الفعلية، مش على إحساس.
المصادر
- Martin Fowler — مقال Circuit Breaker الأصلي على martinfowler.com/bliki/CircuitBreaker.html
- Michael T. Nygard — كتاب Release It! Second Edition، Pragmatic Bookshelf 2018، الفصل 5: Stability Patterns
- مكتبة pybreaker على PyPI — pypi.org/project/pybreaker
- Netflix Hystrix — التوثيق المؤرشف على github.com/Netflix/Hystrix/wiki
- AWS Architecture Blog — Implementing the Circuit Breaker Pattern with AWS Step Functions
- Microsoft Azure Architecture Center — Circuit Breaker pattern