يا جماعة الخير، السلام عليكم ورحمة الله.
اسمحولي أبدأ بقصة صارت معي ومع فريقي قبل كم سنة، قصة بتجسّد معنى “مصائب قوم عند قوم فوائد”. كنا في خضم إطلاق ميزة جديدة وكبيرة في تطبيقنا، الكل متحمس والسهرة صباحي، والقهوة شغّالة على ودنه. النظام تبعنا مبني على معمارية الخدمات المصغرة (Microservices)، يعني مجموعة خدمات صغيرة كل وحدة مسؤولة عن شغلة، وبتحكي مع بعضها.
في ليلة الإطلاق، ومع أول ضغط حقيقي من المستخدمين، بلّشت توصلنا تنبيهات زي المطر. “خدمة A لا تستجيب”، “خدمة B بطيئة جداً”، “خدمة C تعذّر الوصول إليها”. خلال دقايق، النظام كله صار على الأرض. حرفياً، كل إشي توقف. حالة من الهلع في المكتب، والكل بحاول يفهم شو اللي بصير. المشكلة إنه لما نفحص كل خدمة لحالها، بتكون رجعت اشتغلت! بس بمجرد ما يرجع الضغط، بترجع الكارثة.
بعد ساعات من التحليل وتفريغ سجلات الأخطاء (Logs)، اكتشفنا السبب. خدمة صغيرة، مسؤولة عن إرسال الإشعارات (Push Notifications)، كانت بتواجه ضغط عالي وبدأت تفشل وترجع أخطاء. المشكلة الأكبر كانت إن خدمات ثانية كثيرة بتعتمد عليها. مثلاً، خدمة تسجيل مستخدم جديد كانت بتحاول تبعت إشعار ترحيب، فتعلّق وهي بتستنى رد من خدمة الإشعارات الفاشلة. وبهذا الوقت، كل طلبات تسجيل المستخدمين الجدد بتتراكم وبتستهلك كل موارد السيرفر، لحد ما خدمة تسجيل المستخدمين نفسها تفشل. وهذا الفشل ينتقل زي الدومينو لباقي الخدمات… جحيم متتالي من الفشل، أو ما يسمى بـ “Cascading Failure”.
وقتها أدركنا إن مشكلتنا مش بس في الخدمة اللي فشلت، بل في تصميم نظامنا نفسه اللي ما عنده أي “مناعة” ضد فشل جزء منه. وهنا كان لا بد من البحث عن حل جذري، وهنا تعرفنا على منقذنا: نمط قاطع الدائرة أو الـ Circuit Breaker.
ما هو الفشل المتتالي (Cascading Failure)؟ ولماذا هو كابوس المطورين؟
قبل ما نحكي عن الحل، خلينا نفهم المشكلة بعمق. تخيل إنك في مطعم، والمطعم عبارة عن خدمات مصغرة:
- خدمة استقبال الطلبات (API Gateway): النادل الذي يأخذ طلبك.
- خدمة المطبخ (Kitchen Service): الطباخ الذي يجهز الأكل.
- خدمة المشروبات (Drinks Service): الشخص المسؤول عن تجهيز المشروبات.
لو تعطلت ماكينة القهوة في خدمة المشروبات، شو بصير؟
في النظام “الهش”، النادل رح يضل واقف جنب ماكينة القهوة المعطلة يستناها تصلح حالها، وبهذا الوقت كل طلبات الزباين الثانيين (حتى اللي ما بدهم قهوة) بتتأخر، والمطبخ بصير عنده أكل جاهز ما حد بوخده، والزباين بزهقوا وبتركوا المطعم. فوضى عارمة بسبب مشكلة صغيرة واحدة.
هذا بالضبط هو الفشل المتتالي في عالم البرمجيات. خدمة (أ) تطلب من خدمة (ب). خدمة (ب) بطيئة أو فاشلة. خدمة (أ) تظل تنتظر الرد، وتستهلك مواردها (threads, connections, memory). ومع كثرة الطلبات، تنهار خدمة (أ) أيضاً. ثم خدمة (ج) التي تعتمد على (أ) تنهار بدورها… وهكذا.
قاطع الدائرة (Circuit Breaker): كهربائي في قلب نظامك
الفكرة مستوحاة من قاطع الدائرة الكهربائي الموجود في كل بيت. وظيفته بسيطة: إذا زاد الحمل الكهربائي بشكل خطير، القاطع “يفصل” ويقطع التيار عن الدائرة لحماية الأجهزة والأسلاك من الاحتراق. ما بضل يحاول يمرر كهرباء، بل بفصل فوراً.
نمط قاطع الدائرة البرمجي بيعمل نفس المبدأ. هو عبارة عن “بروكسي” أو وسيط ذكي يغلف الاتصال بالخدمة الخارجية (التي قد تفشل). هذا الوسيط له ثلاث حالات:
1. الحالة المغلقة (Closed)
هذا هو الوضع الطبيعي. قاطع الدائرة يكون “مغلقاً”، والطلبات تمر من خلاله إلى الخدمة المطلوبة بدون أي تدخل. في هذه الأثناء، يقوم القاطع بمراقبة النتائج. هل الطلب نجح؟ هل انتهى وقته (timeout)؟ هل رجع بخطأ (مثل خطأ 500)؟
يحتفظ القاطع بعدّاد للأخطاء. إذا زاد عدد الأخطاء عن حد معين خلال فترة زمنية معينة (مثلاً، 10 أخطاء في الدقيقة)، يقرر القاطع أن هناك مشكلة حقيقية، فينتقل إلى الحالة المفتوحة.
2. الحالة المفتوحة (Open)
الآن القاطع “مفتوح”. أي طلب جديد يحاول الوصول للخدمة الفاشلة، القاطع يرفضه فوراً ويرجع خطأ (Fail Fast) بدون حتى محاولة الاتصال بالخدمة. ليش؟
- حماية النظام الطالب (Calling System): بدلاً من أن يظل النظام الطالب معلقاً ينتظر رداً لن يأتي، يحصل على إجابة فورية بالرفض، فيمكنه التصرف بسرعة (مثلاً، عرض رسالة للمستخدم أو استخدام بيانات بديلة).
- إعطاء الخدمة الفاشلة فرصة للتعافي: عندما نتوقف عن إغراق الخدمة المتعثرة بالطلبات، نعطيها “مساحة للتنفس”. قد تكون المشكلة مؤقتة (مثل إعادة تشغيل السيرفر أو تحرير الموارد)، وإيقاف سيل الطلبات يساعدها على التعافي.
يبقى القاطع في الحالة المفتوحة لمدة زمنية محددة مسبقاً (مثلاً، 30 ثانية). بعد انتهاء هذه المدة، ينتقل إلى الحالة التالية.
3. الحالة نصف المفتوحة (Half-Open)
هذه حالة اختبار حذرة. بعد انتهاء فترة الانتظار، يسمح القاطع بمرور طلب واحد فقط (أو عدد قليل جداً من الطلبات) إلى الخدمة. هذا الطلب هو “جس نبض”.
- إذا نجح الطلب: هذا مؤشر جيد على أن الخدمة قد تعافت. عندها، يعود القاطع إلى الحالة المغلقة (Closed) ويعود تدفق الطلبات الطبيعي.
- إذا فشل الطلب: هذا يعني أن المشكلة لا تزال قائمة. يعود القاطع فوراً إلى الحالة المفتوحة (Open) ويبدأ فترة انتظار جديدة.
هذه الآلية الذكية تمنع “الاندفاع” (stampede) نحو الخدمة التي قد تكون لا تزال في مرحلة التعافي.
كيف نطبق قاطع الدائرة عملياً؟
ما في داعي تخترع العجلة من جديد. معظم لغات البرمجة والمنصات لديها مكتبات ممتازة وجاهزة لتطبيق هذا النمط. لا تحاول بناءه من الصفر إلا لأغراض تعليمية.
نصيحة من أبو عمر: دائماً ابحث عن مكتبة موثوقة ومختبرة في بيئة الإنتاج (battle-tested). بناء قاطع دائرة صحيح للتعامل مع الأنظمة المتزامنة (Concurrency) ليس سهلاً كما يبدو.
مثال باستخدام مكتبة (Polly) في بيئة .NET
مكتبة Polly هي من أشهر المكتبات في عالم .NET للتعامل مع مرونة النظم. الكود التالي يوضح كيف يمكن تغليف طلب HTTP بقاطع دائرة.
// C# example using Polly library
// 1. Configure the Circuit Breaker policy
// Break the circuit after 5 consecutive exceptions
// and keep it open for 30 seconds
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (exception, timespan) =>
{
Console.WriteLine("Circuit broken!");
},
onReset: () =>
{
Console.WriteLine("Circuit reset!");
},
onHalfOpen: () =>
{
Console.WriteLine("Circuit is half-open, next call is a trial.");
}
);
// 2. Use the policy to execute your HTTP call
try
{
var httpClient = new HttpClient();
var response = await circuitBreakerPolicy.ExecuteAsync(
() => httpClient.GetAsync("http://unreliable-service/api/data")
);
// ... process the response
}
catch (BrokenCircuitException)
{
// Circuit is open, handle the fallback
Console.WriteLine("Service is unavailable. Falling back to cached data.");
// return cachedData or a default value
}
catch (HttpRequestException)
{
// The initial call failed, but the circuit might still be closed
Console.WriteLine("The call failed, but the circuit is not yet open.");
}
كما ترى، الكود واضح جداً. نحدد السياسة (Policy) أولاً: افصل الدائرة بعد 5 أخطاء متتالية، وأبقها مفتوحة لمدة 30 ثانية. ثم نقوم بتنفيذ طلبنا من خلال هذه السياسة. إذا كانت الدائرة مفتوحة، سترمي المكتبة استثناءً من نوع BrokenCircuitException يمكننا التقاطه والتعامل معه.
نصائح عملية من خبرتي (شغل مرتب)
تطبيق النمط تقنياً سهل، لكن الشيطان يكمن في التفاصيل. إليك بعض النصائح من قلب الميدان:
1. الضبط والمعايرة (Tuning) هو مفتاح النجاح
الأرقام التي تختارها (عدد الأخطاء قبل الفتح، مدة الفتح) مهمة جداً وتعتمد على سياق الخدمة. خدمة حرجة جداً قد تحتاج لأرقام أكثر حساسية من خدمة ثانوية. ابدأ بقيم تقديرية ثم راقب وحسّن بناءً على أداء النظام الحقيقي.
2. لا تنسَ الإجراء البديل (Fallback)
طيب، القاطع فتح… وبعدين؟ ما الفائدة إذا كان كل ما تفعله هو عرض رسالة خطأ قبيحة للمستخدم؟ يجب أن يكون لديك دائماً خطة بديلة.
- بيانات مخبأة (Cache): هل يمكنك إرجاع نسخة قديمة من البيانات؟ هذا أفضل من لا شيء.
- قيمة افتراضية: إذا فشلت خدمة توصيات المنتجات، هل يمكنك عرض قائمة “الأكثر مبيعاً” الثابتة؟
- تجربة مستخدم أبسط: إذا فشلت خدمة الصور الرمزية (Avatars)، اعرض الحرف الأول من اسم المستخدم بدلاً من صورة مكسورة.
الإجراء البديل هو ما يحول النظام من “صامد” إلى “مرن وذكي”.
3. المراقبة والتنبيهات (Monitoring and Alerting)
قاطع الدائرة ليس حلاً سحرياً يوضع ويُنسى. يجب أن تعرف متى “يفصل”. قم بإعداد تنبيهات (Alerts) عندما ينتقل قاطع دائرة معين إلى الحالة المفتوحة بشكل متكرر. هذا مؤشر قوي على وجود مشكلة عميقة في الخدمة التابعة تحتاج إلى اهتمامك.
4. ادمجه مع أنماط مرونة أخرى
قاطع الدائرة يصبح أقوى عندما يعمل مع أصدقائه:
- نمط إعادة المحاولة (Retry): قبل أن يعتبر قاطع الدائرة الطلب فاشلاً، قد يكون من المفيد إعادة المحاولة مرة أو مرتين مع فاصل زمني قصير. أحياناً يكون الفشل عابراً (transient).
- نمط المهلة الزمنية (Timeout): لا تجعل خدمتك تنتظر إلى الأبد. حدد مهلة زمنية قصيرة ومعقولة لكل طلب خارجي.
– نمط العزل (Bulkhead): يمنع هذا النمط من أن يؤثر فشل خدمة ما على جميع موارد التطبيق، بل يعزل المشكلة في جزء صغير، مثلما تفعل الحواجز في السفن لمنع غرقها بالكامل إذا تسرب الماء إلى إحدى الحجرات.
الخلاصة: من الفوضى إلى الصمود 💪
في عالم الأنظمة الموزعة والخدمات المصغرة، الفشل ليس احتمالاً، بل هو حقيقة واقعة ومؤكدة. مهمتنا كمطورين ومهندسين ليست بناء أنظمة لا تفشل أبداً (لأن هذا مستحيل)، بل بناء أنظمة تستطيع الصمود والتعافي بأناقة عندما يحدث الفشل.
نمط قاطع الدائرة هو واحد من أهم الأدوات في صندوق عدة المرونة (Resilience Toolkit). يحول الفشل المتتالي الكارثي إلى مشكلة معزولة ومُحتواة، ويمنح نظامك القدرة على “شفاء نفسه” ذاتياً. قصتنا تلك الليلة كانت مؤلمة، لكنها علمتنا درساً لن ننساه أبداً: بناء نظام مرن ليس ترفاً، بل هو أساس استمرارية عملك وراحة بالك.
فيا صديقي المطور، لا تنتظر حدوث الكارثة. تفقد خدماتك، وحدد نقاط الضعف، وابدأ بتطبيق قواطع الدائرة اليوم. نظامك ومستخدموك سيشكرونك لاحقاً. ✅
يلا، شدّوا حيلكم!