المستوى المطلوب: متوسط — تحتاج فهم أساسي لاستدعاءات الـ HTTP بين الخدمات، الـ async/await في JavaScript، وفكرة الـ thread pool. لو ما اشتغلتش قبل كده مع API خارجي ولا فهمت ليه السيرفر بيوقع لما خدمة تابعة تتعطّل، ابدأ بمقالات الأساسيات الأول وارجع للمقال ده بعدها.
لو خدمة الدفع عندك بتنادي Stripe، وStripe وقع لمدة دقيقة، كل طلب بيستنى رد بياكل thread أو connection لمدة 30 ثانية. مع 500 طلب في الثانية، الـ thread pool بيتسد في ثوان معدودة. النتيجة: السيرفر بتاعك بيرد ببطء على كل حاجة، حتى الـ endpoints اللي ملهاش علاقة بالدفع. Circuit Breaker بيحل المشكلة دي بسطر واحد من القرار: لما الخدمة الخارجية تكون فاشلة، بلاش تكلمها أصلاً.
المشكلة باختصار
المشكلة بسيطة في توصيفها، صعبة في تأثيرها. خدمة تابعة بتقع. الكود بتاعك مش عارف، فبيكمل يبعتلها طلبات. كل طلب بياخد وقت timeout كامل قبل ما يفشل. الـ thread pool بتاعك بيتسد. الطلبات السليمة من ميزات تانية بتفضل في الطابور. السيرفر كله بيرد ببطء، مش بس الميزة المعطلة. بعد دقيقتين، الـ load balancer بيشيل السيرفر من الـ pool لأنه health check بيفشل. السيرفر التاني بياخد كل الحمل، بيقع كمان. اللي بدأ مشكلة في خدمة Stripe بقى outage في كل النظام.
المثال البسيط: عداد الكهرباء في البيت
تخيّل عندك تكييف وقع فيه short circuit. لو ما فيش مفتاح حماية في عداد الكهرباء، التيار هيكمل يتدفق وهيولّد حرارة وممكن يولّع حريقة في الشقة كلها. مفتاح الحماية بيقطع التيار في أقل من ثانية لما يحس بالخطأ، وبيفصل لحد ما حد يأكد إن الجهاز اتصلح ويدوس Reset.
Circuit Breaker في الكود بيشتغل بنفس المنطق بالظبط. هو طبقة بين الخدمة بتاعتك والخدمة التانية. بيعد الفشل. لو وصل لحد معين، بيقفل المفتاح. أي طلب جديد بيرجع فشل فوري بدون ما يحاول أصلاً يكلم الخدمة المعطلة. بعد فترة، بيسمح بطلب واحد يجرّب يعدّي. لو نجح، بيرجع الوضع لطبيعته. لو فشل، بيقفل تاني.
التعريف العلمي الدقيق
Circuit Breaker pattern طرحه Michael Nygard في كتابه "Release It!" سنة 2007، وبقى من أساسيات الـ resilience engineering في الأنظمة الموزّعة. تقنيًا، هو state machine بثلاث حالات:
- Closed: الوضع الطبيعي. الطلبات بتعدّي بشكل عادي للخدمة التابعة. الـ breaker بيعد عدد الفشلات في نافذة زمنية محددة.
- Open: الفشلات تخطّت الحد المسموح به (threshold). أي طلب بيرجع فشل فوري بدون ما يستدعي الخدمة التابعة. بيستمر في الحالة دي لفترة محددة (reset timeout).
- Half-Open: بعد انتهاء فترة الـ timeout، بيسمح بعدد محدود من الطلبات (probe requests) تجرّب الخدمة. لو نجحت، بيرجع لـ Closed. لو فشلت، بيرجع لـ Open ويعيد العداد.
الفلسفة الأساسية: fail fast بدل fail slow. فشل سريع بيخلي السيستم يستجيب للضغط. فشل بطيء بياكل الموارد ويعدّي العدوى لباقي النظام.
كود Node.js شغّال من 35 سطر
الكود ده مينفعش لـ production من غير metrics وlogging، لكنه بيوضّح المنطق كاملًا:
class CircuitBreaker {
constructor(action, { threshold = 5, timeout = 10000 } = {}) {
this.action = action;
this.threshold = threshold;
this.timeout = timeout;
this.failures = 0;
this.state = 'CLOSED';
this.nextAttempt = 0;
}
async fire(...args) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await this.action(...args);
this.failures = 0;
this.state = 'CLOSED';
return result;
} catch (err) {
this.failures++;
if (this.failures >= this.threshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
}
throw err;
}
}
}
const callPayment = async (orderId) => fetch(`/pay/${orderId}`).then(r => r.json());
const breaker = new CircuitBreaker(callPayment, { threshold: 5, timeout: 10000 });
await breaker.fire('order-123');قياس فعلي: قبل وبعد
السيناريو: API دفع بيستقبل 500 طلب/ثانية. Stripe وقع لمدة 60 ثانية. Timeout الـ HTTP الافتراضي 30 ثانية. الـ thread pool عند 200 thread.
- بدون Circuit Breaker: أول 0.4 ثانية الـ thread pool امتلأ. بعد ثانية، الطلبات على الـ endpoints التانية (search, profile, products) بدأت تتأخر. بعد 5 ثواني، latency متوسط على كل النظام طلع لـ 4 ثواني. health check فشل. الـ load balancer شال السيرفر.
- مع Circuit Breaker (threshold=5، timeout=10s): أول 5 طلبات فشلت في حدود 30 ثانية. بعدها كل طلب على endpoint الدفع بيرجع "Circuit Open" في أقل من 1ms. الـ thread pool فضل متاح للـ endpoints السليمة. متوسط latency على باقي النظام فضل 18ms. الميزة المعطلة وحدها بترجع خطأ، الباقي شغّال.
الفرق هنا مش 10% أو 20%. الفرق إن السيستم لسه بيشتغل أصلًا.
الـ Trade-offs الحقيقية اللي ما بيقولوهاش
المكسب: حماية الموارد، fail-fast، عزل الفشل، تحسّن في تجربة المستخدم بدل ما يستنى 30 ثانية. التكلفة: تعقيد إضافي في الكود، state بقى محتاج تتبع، false positives لو الـ threshold قليل أوي. أهم نقطة: الطلبات اللي بترجع "Circuit Open" مش بترجع للمستخدم رد سليم. لازم يكون عندك fallback (cache قديم، رسالة واضحة، أو قيمة افتراضية) بدل ما تظهر للمستخدم خطأ خام. Netflix Hystrix كان بيركّز على ده تحديدًا.
الـ trade-off هنا: بتكسب استقرار النظام، بتخسر بساطة الكود. الافتراض إن الفشلات في الخدمة التابعة بتيجي على شكل clusters (لما تقع، بتقع لفترة)، مش فشلات متفرقة عشوائية.
متى لا تستخدم Circuit Breaker
الافتراض الأساسي إن الخدمة التابعة بتفشل بشكل متكرر ومش-تدريجي. لو الخدمة بتاعتك بتتعامل مع موارد محلية فقط (database داخلي مثلاً، أو cache على نفس الجهاز)، الأنسب timeout قصير + retry محدود مع backoff. Circuit Breaker هنا بيضيف تعقيد بدون فايدة حقيقية.
كمان مش مناسب لو فشل العملية بيؤثر على state بطريقة معقدة (operations بتعدّل في multiple databases). هنا بتحتاج Saga pattern مش Circuit Breaker لوحده. وأخيرًا، للـ background jobs اللي مش حساسة للـ latency، queue + retry policy غالبًا أبسط وأكفأ.
الخطوة التالية
افتح الكود بتاعك دلوقتي ودوّر على أي مكان فيه استدعاء API خارجي (`fetch`, `axios`, `http.request`). اسأل نفسك: لو الخدمة دي وقعت 60 ثانية، السيرفر بتاعي هيوقع معاها؟ لو الإجابة آه، لف المكالمة دي في Circuit Breaker بسيط (الكود اللي فوق ينفع كبداية فورية، أو استخدم مكتبة جاهزة زي opossum على Node.js أو Resilience4j على Java). قِس عدد الـ active threads قبل وبعد. لو في فرق ملحوظ، يبقى المقال نفع.
المصادر
- Michael Nygard, "Release It! Design and Deploy Production-Ready Software", Pragmatic Bookshelf, الطبعة الأولى 2007 والطبعة الثانية 2018.
- Martin Fowler, "CircuitBreaker", martinfowler.com (مارس 2014).
- AWS Builders' Library, "Avoiding fallback in distributed systems" — aws.amazon.com/builders-library.
- Netflix Tech Blog, "Making the Netflix API More Resilient" (2011) — مقدمة Hystrix.
- Resilience4j Documentation — resilience4j.readme.io/docs/circuitbreaker.
- Microsoft Azure Architecture Center, "Circuit Breaker pattern" — learn.microsoft.com/azure/architecture/patterns/circuit-breaker.
- opossum GitHub repository — github.com/nodeshift/opossum (مكتبة Circuit Breaker الأشهر على Node.js).