يا جماعة الخير، السلام عليكم ورحمة الله.
اسمحوا لي أن أرجع بالذاكرة ليلة لن أنساها ما حييت. كانت ليلة ثلاثاء عادية، وفريقنا كان يحتفل بإطلاق ميزة جديدة في نظامنا القائم على معمارية الخدمات المصغرة (Microservices). كل شيء كان يبدو على ما يرام، لوحات المراقبة (Dashboards) خضراء، والمستخدمون سعداء. وفجأة، حوالي الساعة الحادية عشرة ليلاً، بدأت هواتفنا ترن كأنها جرس إنذار حريق. النظام بطيء… النظام لا يستجيب… خطأ 503 في كل مكان!
الله وكيلكم، هرعنا إلى مكاتبنا، وأكواب القهوة الباردة تشهد على ليلة طويلة. بعد ساعات من التحليل والضغط النفسي، اكتشفنا المصيبة. خدمة صغيرة، وغير حيوية بالمرة، كانت مسؤولة عن توليد “اقتراحات مشابهة” للمنتجات. هذه الخدمة واجهت مشكلة في قاعدة بياناتها وبدأت بالتباطؤ ثم الفشل في الاستجابة. لكن الكارثة لم تكن في فشل هذه الخدمة، بل في أن كل الخدمات الأخرى التي كانت تستدعيها (مثل خدمة صفحة المنتج، خدمة سلة المشتريات) بقيت تنتظر الرد منها حتى انتهاء مهلة الاتصال (Timeout).
هذا الانتظار استنزف كل “الخيوط” (Threads) في الخدمات المستدعية، مما أدى إلى شللها تمامًا. فشل خدمة واحدة، غير مهمة، تسبب في انهيار متتالٍ للنظام بأكمله. هذا ما نسميه “جحيم الفشل المتتالي” (Cascading Failures). في تلك الليلة، أقسمنا أن هذا السيناريو لن يتكرر. ومن هنا بدأت رحلتنا الحقيقية مع بطل قصتنا: نمط قاطع الدائرة (Circuit Breaker).
ما هو نمط قاطع الدائرة (Circuit Breaker)؟
قبل أن ندخل في التفاصيل التقنية، دعونا نأخذ مثالاً من حياتنا اليومية. في كل بيت، يوجد قاطع كهرباء. وظيفته بسيطة: إذا حدث حمل زائد أو تماس كهربائي، “يفصل” القاطع التيار عن جزء من المنزل ليحمي الأجهزة وباقي الشبكة من الاحتراق. إنه لا يحاول إعادة توصيل التيار كل ثانية، بل يبقى مفصولاً حتى تذهب أنت وتصلح المشكلة ثم تعيد تشغيله يدويًا.
نمط قاطع الدائرة في عالم البرمجيات يفعل الشيء نفسه بالضبط، ولكن بشكل آلي. إنه نمط تصميم يُستخدم لحماية نظامك من خلال إيقاف محاولات الاتصال المتكررة بخدمة من الواضح أنها فاشلة أو لا تستجيب. بدلاً من استنزاف الموارد في محاولات يائسة، “يفتح” القاطع الدائرة ويمنع أي اتصال جديد لفترة زمنية محددة، مما يمنح الخدمة الفاشلة فرصة للتعافي والنظام المستدعي فرصة لالتقاط أنفاسه.
كيف يعمل قاطع الدائرة؟ الحالات الثلاث
يعمل قاطع الدائرة من خلال المرور بثلاث حالات رئيسية، تمامًا مثل مفتاح الضوء الذي له وضع التشغيل والإيقاف، لكن مع حالة ثالثة ذكية جدًا.
1. الحالة المغلقة (Closed)
هذا هو الوضع الطبيعي. يكون القاطع “مغلقًا”، والتيار (أي طلبات الشبكة) يمر من خلاله إلى الخدمة البعيدة. في هذه الحالة، يقوم قاطع الدائرة بمراقبة عدد مرات الفشل. إذا نجح الاتصال، يتم إعادة تعيين عداد الفشل. أما إذا فشل، فيزيد العداد بواحد. عندما يصل عدد مرات الفشل المتتالية إلى حد معين (أنت تحدده، مثلاً 5 مرات)، يتغير وضع القاطع.
مثال: خدمة المنتجات تستدعي خدمة التوصيات. كل شيء على ما يرام. القاطع في حالة “مغلقة”.
2. الحالة المفتوحة (Open)
عندما يتجاوز عدد مرات الفشل الحد المسموح به، “يطق” القاطع ويفتح الدائرة. في هذه الحالة، أي محاولة جديدة لاستدعاء الخدمة الفاشلة سيتم رفضها فورًا (Fail-Fast) دون حتى محاولة إجراء اتصال شبكي. يتم إرجاع خطأ فوري للعميل أو يتم تنفيذ منطق بديل (Fallback). هذا هو السحر الحقيقي! أنت لا تضيع وقتًا أو موارد (Threads, Sockets) في انتظار خدمة ميتة.
يبقى القاطع في هذه الحالة لفترة زمنية محددة (مثلاً 30 ثانية). بعد انقضاء هذه المدة، لا يعود مباشرة إلى الحالة المغلقة، بل ينتقل إلى حالة اختبار ذكية.
مثال: خدمة التوصيات فشلت 5 مرات متتالية. القاطع يفتح الدائرة. الآن، أي طلب جديد من خدمة المنتجات سيُرفض فورًا، وربما نعرض للمستخدم قائمة منتجات عامة بدلاً من التوصيات المخصصة.
3. حالة نصف المفتوحة (Half-Open)
بعد انتهاء فترة الانتظار في الحالة المفتوحة، يدخل القاطع في حالة “نصف مفتوحة”. في هذا الوضع، يسمح بمرور طلب واحد فقط، طلب “جس النبض”.
- إذا نجح هذا الطلب: يعتبر القاطع أن الخدمة قد تعافت، فيعود إلى الحالة “المغلقة” ويعيد تعيين عداد الفشل. الحياة تعود إلى طبيعتها.
- إذا فشل هذا الطلب: يستنتج القاطع أن الخدمة لا تزال تعاني من مشاكل، فيعود فورًا إلى الحالة “المفتوحة” ويبدأ مؤقتًا جديدًا (Timeout).
هذه الحالة تمنع “عاصفة” من الطلبات على خدمة قد تكون لا تزال في طور التعافي، مما قد يؤدي إلى انهيارها مرة أخرى.
مثال عملي: لننقذ خدمة التوصيات (باستخدام C# و Polly)
الحكي سهل، خلينا نشوف كود. في عالم .NET، تعتبر مكتبة Polly هي المعيار الذهبي لتطبيق أنماط المرونة (Resilience Patterns) مثل قاطع الدائرة. لنرى كيف كنا سننقذ ليلتنا المشؤومة باستخدامها.
السيناريو: لدينا `ProductsController` يحاول الحصول على توصيات من `RecommendationService`.
الكود قبل قاطع الدائرة (الطريق إلى الجحيم)
// ProductsController.cs
[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(int id)
{
var product = await _productService.GetById(id);
if (product == null)
{
return NotFound();
}
// هذا هو الاستدعاء الذي يسبب الكارثة
// إذا كانت خدمة التوصيات بطيئة، سيتجمد هذا الخيط (Thread)
try
{
var recommendations = await _recommendationClient.GetRecommendationsFor(id);
product.Recommendations = recommendations;
}
catch (Exception ex)
{
// الخدمة فشلت، لكننا سنحاول مرة أخرى في الطلب التالي... وهكذا
_logger.LogError(ex, "Failed to get recommendations");
// سنرجع المنتج بدون توصيات، لكن الضرر على مستوى النظام قد حصل
}
return Ok(product);
}
المشكلة هنا هي أن `_recommendationClient` سيستمر في المحاولة مع كل طلب، مما يستنزف موارد الخادم.
الكود بعد استخدام قاطع الدائرة مع Polly (الخلاص)
أولاً، نقوم بتعريف سياسة قاطع الدائرة عند إعداد خدماتنا (في `Program.cs` أو `Startup.cs`).
// Program.cs
// تعريف سياسة قاطع الدائرة
var circuitBreakerPolicy = HttpPolicyExtensions
.HandleTransientHttpError() // التعامل مع أخطاء الشبكة الشائعة
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 3, // اسمح بـ 3 محاولات فاشلة
durationOfBreak: TimeSpan.FromSeconds(30) // افتح الدائرة لمدة 30 ثانية
);
// ربط السياسة مع HttpClient الخاص بخدمة التوصيات
builder.Services.AddHttpClient<IRecommendationClient, RecommendationClient>()
.AddPolicyHandler(circuitBreakerPolicy);
هذا كل شيء! الآن، `HttpClient` الذي يستخدمه `RecommendationClient` محمي بسياسة قاطع الدائرة. لا حاجة لتغيير الكود في الـ Controller.
ماذا يحدث الآن؟
- الطلب الأول والثاني والثالث لخدمة التوصيات يفشل.
- عند الطلب الرابع، سياسة Polly تفتح الدائرة. بدلاً من إرسال طلب شبكي، سترمي استثناء `BrokenCircuitException` فورًا.
- لمدة 30 ثانية، أي طلب جديد سيرمي نفس الاستثناء فورًا، مما يحمي خدمتنا `ProductsController` من الانتظار.
- بعد 30 ثانية، ستدخل السياسة في وضع `Half-Open` وتسمح بطلب واحد. إذا نجح، تعود الدائرة للوضع `Closed`. إذا فشل، تعود للوضع `Open` لـ 30 ثانية أخرى.
نصائح من خبرة أبو عمر
تطبيق النمط سهل، لكن الحكمة في استخدامه. إليكم بعض النصائح من وحي التجربة:
- لا تطبقه على كل شيء: قاطع الدائرة مثالي للاتصالات مع خدمات ثانوية أو غير حيوية. إذا كانت الخدمة التي تستدعيها حيوية جدًا (مثل خدمة المصادقة – Authentication)، فإن فشلها يعني أن نظامك لا يمكنه العمل على أي حال. في هذه الحالة، قد يكون “الفشل السريع” هو المطلوب دون الحاجة لقاطع دائرة.
- اضبط إعداداتك بحكمة: الأرقام التي تختارها (عدد المحاولات قبل الفتح، مدة الفتح) مهمة جدًا. إذا كانت حساسة جدًا، قد تفتح الدائرة بسبب مشكلة عابرة. إذا كانت غير حساسة، فقد لا تحمي نظامك بالسرعة الكافية. ابدأ بقيم معقولة وراقب سلوك النظام وعدّلها بناءً على البيانات.
- ماذا تفعل عندما يكون القاطع مفتوحًا؟ (منطق الـ Fallback): هذه أهم نصيحة. لا تكتفِ بإرجاع خطأ للمستخدم. هذا سلوك سيء. جهّز “خطة ب”. عندما يكون القاطع مفتوحًا، قم بتنفيذ منطق بديل. في مثالنا، بدلاً من إرجاع خطأ، يمكننا إرجاع قائمة توصيات عامة محفوظة مسبقًا (Cached)، أو حتى قائمة فارغة. هذا ما يسمى “التدهور التدريجي” (Graceful Degradation)، وهو يحسن تجربة المستخدم بشكل كبير.
- المراقبة والتنبيهات: عندما “يطق” قاطع دائرة، هذا ليس مجرد خطأ، بل هو مؤشر قوي على أن إحدى خدماتك في خطر. يجب أن يكون لديك نظام مراقبة يطلق تنبيهًا لفريق العمل عندما يدخل قاطع دائرة في الحالة المفتوحة. هذا يتيح لك التدخل بشكل استباقي قبل أن يلاحظ المستخدمون أي شيء.
الخلاصة: من الفوضى إلى المرونة ✅
يا جماعة، في عالم الأنظمة الموزعة والخدمات المصغرة، الفشل ليس احتمالاً، بل هو حقيقة واقعة ومسألة وقت. المطور الذكي لا يسعى لبناء نظام لا يفشل أبدًا، بل يبني نظامًا يستطيع الصمود والتعافي عند حدوث الفشل. نمط قاطع الدائرة هو أحد أهم الأدوات في صندوق عدتنا لتحقيق هذه المرونة (Resilience).
لقد حوّلنا نظامنا من بناءٍ من زجاج ينهار عند أول هزة، إلى قلعة مرنة قادرة على عزل الأضرار ومواصلة العمل حتى في أحلك الظروف. تذكروا دائمًا: لا تدعوا فشل جزء صغير من نظامكم يدمر الكل. ابنوا دفاعاتكم، واستخدموا قواطع الدوائر بحكمة.
أتمنى لكم كل التوفيق في بناء أنظمة قوية وصامدة. 💪