الموثوقية والإتاحة والمرونة

قواطع الدوائر والفواصل العازلة

18 دقيقة الدرس 5 من 10

قواطع الدوائر والفواصل العازلة

قد تُدمّر خدمة واحدة بطيئة أو متعطّلة في المراحل اللاحقة النظامَ بأكمله. فبدون حماية صريحة، تمتلئ مجمّعات الخيوط البرمجية بانتظار انتهاء مهل الاتصال، وتتراكم الطلبات في الذاكرة دون إجابة، وتنتشر زيادة زمن الاستجابة للأعلى باتجاه المستدعين — وهو ما يُعرف بـالفشل المتسلسل. يوجد نمطان متكاملان للحماية: قاطع الدائرة الذي يعزل التبعية المعطلة بمرور الوقت، والفاصل العازل الذي يحصر الفشل في مجمّع موارد محدود حتى يظل بقية النظام سليمًا.

الفشل المتسلسل: المشكلة الجوهرية

تخيّل أن الخدمة A تستدعي الخدمة B، التي تستدعي الخدمة C. بدأت C تعاني من ازدحام قاعدة البيانات فباتت تستجيب في 30 ثانية عوضًا عن 50 ميلي ثانية. يمتلئ مجمّع خيوط B بخيوط محجوبة تنتظر C. تبدأ استدعاءات A على B بالحجب هي الأخرى، ممّا يُفرغ مجمّع A. في غضون دقائق، تنهار سلسلة الاستدعاء بالكامل — فيما كانت C بطيئة فقط لا مُعطّلة. هذا هو سمة الفشل المتسلسل.

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

Cascading failure vs. circuit breaker containment Without Circuit Breaker Service A Pool full Service B Threads blocked Service C 30 s latency slow slow Failure spreads upstream With Circuit Breaker Service A Healthy CIRCUIT OPEN Service B Degraded blocked Fast failure returned immediately — A stays up Circuit Breaker State Machine CLOSED OPEN HALF-OPEN (probe) threshold breached timeout elapsed probe succeeds → reset probe fails → back OPEN
آلة الحالة لقاطع الدائرة: CLOSED (طبيعي)، OPEN (فشل سريع)، HALF-OPEN (اختبار الاسترداد).

نمط قاطع الدائرة

صاغ مايكل نيغارد هذا النمط في كتابه Release It!؛ يُغلّف قاطع الدائرة كل استدعاء خارجي للتبعية ويتتبع معدل النجاح والفشل. يمر القاطع بثلاث حالات:

  • CLOSED (مغلق) — تمر الاستدعاءات بشكل طبيعي. تُحسب الأخطاء في نافذة زمنية منزلقة (آخر 60 ثانية أو آخر 100 استدعاء). إذا تجاوز معدل الخطأ عتبةً محددة — كـ50% — ينتقل القاطع إلى حالة OPEN.
  • OPEN (مفتوح) — تفشل جميع الاستدعاءات فورًا دون لمس التبعية. يحصل المستدعي على خطأ في ميكروثوانٍ عوضًا عن انتظار 30 ثانية. هذه هي حالة "الفشل السريع".
  • HALF-OPEN (نصف مفتوح) — بعد نافذة انتظار محددة (مثلاً 10 ثوانٍ)، يسمح القاطع لعدد صغير من الطلبات التجريبية بالمرور. إن نجحت، يُعاد ضبط القاطع على CLOSED. وإن فشلت، يعود إلى OPEN وينتظر من جديد.
الفكرة الجوهرية: لا يُصلح قاطع الدائرة الخدمة اللاحقة المتعطّلة — بل يحمي المستدعي من إهدار الموارد أثناء عدم صحة التبعية. لا يزال الاسترداد بحاجة إلى الحدوث على الجانب الآخر؛ القاطع يكسب الوقت فقط دون أن تتسلسل الأضرار.

عتبات عملية من أنظمة الإنتاج: تُفعّل Netflix Hystrix القاطع افتراضيًا عند 50% معدل خطأ على 20 طلبًا في نافذة 10 ثوانٍ مع نافذة انتظار 5 ثوانٍ. تستخدم Resilience4j (خليفتها الحديثة بلغة Java) مخزنًا حلقيًا من 100 استدعاء بنفس النسبة الافتراضية. يجب ضبط هذه الأرقام لكل خدمة بناءً على معدل أخطائها الطبيعي.

ماذا يُرجَع حين تكون الدائرة مفتوحة؟

لا ينبغي لقاطع الدائرة المفتوح أن يقذف استثناءً وحسب. أمام المستدعي ثلاثة خيارات جيدة:

  1. إرجاع استجابة مخزّنة/قديمة — إذا كانت القيمة الأخيرة المعروفة مقبولة (كسعر منتج من 5 دقائق مضت)، أرجِعها مع مؤشر على حداثتها.
  2. إرجاع استجابة مخفّضة/افتراضية — أرجِع قائمة فارغة، أو مجموعة توصيات افتراضية، أو علامة "الميزة غير متاحة". يتدهور تجربة المستخدم بأمان بدلًا من الانهيار.
  3. إضافة الطلب إلى قائمة انتظار للإعادة اللاحقة — لعمليات الكتابة، اخزّن الطلب في قائمة محلية وأعِد المحاولة حين يُغلق القاطع.
أفضل ممارسة: حدّد بديلًا لكل قاطع دائرة عند التصميم، لا كفكرة لاحقة. السؤال الذي ينبغي طرحه: ما أقل تجربة سيئة للمستخدم حين تكون هذه التبعية غير متاحة؟

نمط الفاصل العازل

يُحدّ الفاصل العازل (مستوحى من الحجرات المانعة للماء في بدن السفينة) نطاق تأثير الفشل بمنح كل تبعية أو فئة عمل مجمّع موارد معزولًا خاصًا بها. الفكرة بسيطة: إذا كانت الخدمة A تستدعي كلًا من B وC، فإن إبطاء B لا ينبغي أن يُمكّنها من استهلاك كل خيوط A وتجويع استدعاءات C.

Bulkhead pattern: isolated thread pools per dependency Service A 200 threads total Bulkhead Isolation Pool B — 40 threads SATURATED (slow dep.) Pool C — 40 threads Available (healthy) Shared — 120 threads Service B 30 s latency Service C 50 ms — OK Pool B saturation does NOT affect Pool C or Shared pool
عزل الفاصل العازل: كل تبعية تحصل على مجمّع خيوطها المخصص، فلا يستطيع مجمّع مشبع واحد تجويع البقية.

يوجد تطبيقان شائعان للفواصل العازلة:

  • الفاصل العازل بمجمّع الخيوط — تحصل كل تبعية على مجمّع خيوط ثابت الحجم. تُوضع الاستدعاءات في قائمة انتظار داخل ذلك المجمّع؛ وحين يمتلئ، تُرفض الاستدعاءات الجديدة فورًا. يُستخدم في Hystrix وResilienc4j مع أوامر مدعومة بـExecutorService.
  • الفاصل العازل بالإشارة التزامنية — بدلًا من مجمّع خيوط منفصل، تُقيّد إشارة تزامنية عدد الاستدعاءات المتزامنة قيد التنفيذ. أخف وزنًا (بلا خيوط إضافية)، لكنه لا يستطيع فرض مهلة على الاستدعاء نفسه لأنه يعمل على خيط المستدعي.
تحذير: مجمّع اتصالات HTTP مشترك بين جميع التبعيات يُبطل الفواصل العازلة كليًا. إذا استهلكت الخدمة B جميع الـ200 اتصال، فإن استدعاءات C ستنتظر في مستوى مجمّع الاتصالات قبل أن تصل إلى الفاصل العازل. احرص دومًا على تهيئة مجمّعات اتصالات مستقلة لكل تبعية بجانب إشاراتك التزامنية أو مجمّعات خيوطك.

قواطع الدوائر والفواصل العازلة معًا

هذان النمطان متكاملان لا بديل أحدهما للآخر. فكّر فيهما كخطّين دفاعيين:

  1. الفاصل العازل يحدّ كمية الموارد التي يمكن لتبعية فاشلة استهلاكها — يُقيّد نطاق الضرر في الحجم.
  2. قاطع الدائرة يوقف الاستدعاء كليًا حين تكون التبعية مريضة بوضوح — يُقيّد نطاق الضرر في الزمن.

في نظام مصمّم جيدًا، يعزل الفاصل العازل التدهور المبكر (B بطيئة لكنها لم تتجاوز عتبة الخطأ بعد)، بينما يتدخّل قاطع الدائرة إذا تفاقم الأمر. تُيسّر Resilience4j تركيب الاثنين: عُلّم التابع بـ@Bulkhead و@CircuitBreaker، هيّئ خصائص مستقلة لكل تبعية في application.yml، ودع الإطار يتولى تتبّع الحالة.

مثال من الواقع: تستخدم بوابة API في Netflix كليهما. لكل خدمة لاحقة (Recommendations وRatings وSearch) مجمّع خيوطها المخصص (فاصل عازل) بنحو 40 خيطًا. ولكل منها أيضًا قاطع دائرة بعتبة خطأ 50%. خلال حادثة عام 2012، تدهورت خدمة Recommendations؛ امتلأ مجمّع خيوطها، فُتح القاطع، وحصل كل طلب للصفحة الرئيسية على قائمة توصيات افتراضية محسوبة مسبقًا من الذاكرة المخبّئة. ظل التصفّح يعمل بكامل طاقته — التوصيات الشخصية وحدها كانت مفقودة. بدون هذين النمطين، كان كل تحميل للصفحة الرئيسية سينتهي بمهلة اتصال.

معاملات التهيئة الرئيسية للضبط

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

  • الحجم الأدنى للاستدعاءات — لا تُفعّل القاطع بعد خطأين من أصل استدعاءين. اشترط 20 استدعاءً على الأقل في النافذة قبل تقييم معدل الخطأ.
  • عتبة الاستدعاء البطيء — اعتبر الاستدعاء "بطيئًا" (واحسبه ضمن معدل الخطأ) إذا تجاوز عتبة زمن استجابة (مثلاً 2 ثانية). الاستدعاءات البطيئة لا تقل خطورة عن الأخطاء.
  • نافذة الانتظار — مدة حالة OPEN قبل الاختبار. قصيرة جدًا: تضغط على خدمة مرهقة أصلًا. طويلة جدًا: تُؤخّر الاسترداد دون مبرر. النطاق الشائع 5–30 ثانية.
  • حجم الفاصل العازل — قم بتحجيم مجمّعات الخيوط بناءً على التزامنية المُلاحَظة، لا بالأمل. إذا كان الاستدعاء يستغرق 200 ميلي ثانية في المتوسط وتريد معالجة 100 طلب/ثانية، فأنت بحاجة إلى 20 خيطًا على الأقل (قانون Little: N = λ × W).