مقدمة: عندما يصبح الفشل صديقك 🤝
بتذكر مرة، كنا شغالين على مشروع كبير لشركة اتصالات. كنا مبسوطين بالتكنولوجيا اللي استخدمناها، وكل شي كان ماشي تمام… لحد ما صار أول انهيار 💥. خدمة رئيسية وقعت، والنظام كله صار يعلق. قعدنا ساعات ندور على السبب، واكتشفنا إنو مشكلة بسيطة في كود صغير كانت السبب في سلسلة من المشاكل المتراكمة. من يومها، فهمت إنو بناء أنظمة قوية مش بس بيعتمد على الكود النظيف والتصميم الجيد، بل كمان على توقع الفشل والاستعداد للتعامل معه بذكاء. هاي هي فكرة أنماط الاستقرار والمرونة (Resilience Patterns).
في الأنظمة عالية الضغط، الفشل مش احتمال، هو حقيقة. لازم نصمم النظام ليفشل بذكاء (Fail Gracefully) بدل ما ينهار بشكل كامل. خلينا نشوف كيف ممكن نعمل هيك.
6.1 قاطع الدائرة (Circuit Breaker): حماية النظام من الانهيار المتسلسل ⚡
هذا النمط مستوحى من الهندسة الكهربائية. تخيل عندك في البيت قاطع كهربائي بيفصل التيار لما يصير حمل زائد. نفس الفكرة هنا.
المشكلة
إذا خدمة المدفوعات تعطلت، خدمة الطلبات اللي بتعتمد عليها رح تضل تحاول تتصل وتستنى الرد (Timeouts). هذا بيستهلك كل خيوط المعالجة (Threads) وبيوقف النظام بالكامل. زي لما كل لمبات البيت تضوي على نفس الخط الكهربائي، فبيحرق الفيوز.
الحل
قاطع الدائرة بيراقب معدل الأخطاء.
- Closed: الوضع الطبيعي، الطلبات بتمر.
- Open: عند تجاوز حد الأخطاء، بتفتح الدائرة وبتترفض كل الطلبات فوراً بدون محاولة الاتصال بالخدمة المعطلة. هذا بيوفر الموارد وبيعطي الخدمة المعطلة وقت تتعافى.
- Half-Open: بعد فترة، بيسمح بمرور عدد محدود من الطلبات كـ “اختبار”. إذا نجحت، بتسكر الدائرة؛ وإذا فشلت، بترجع للفتح.
مثال كود (Java باستخدام Spring Cloud Circuit Breaker):
@Service
public class PaymentService {
@Autowired
private RestTemplate restTemplate;
@CircuitBreaker(name = "paymentService", fallbackMethod = "paymentServiceFallback")
public String processPayment(Order order) {
// Call payment service
return restTemplate.postForObject("http://payment-service/pay", order, String.class);
}
public String paymentServiceFallback(Order order, Throwable t) {
// Handle failure gracefully, e.g., return a cached response or default value
return "Payment service is unavailable. Please try again later.";
}
}
نصيحة من أبو عمر: حدد قيم مناسبة لحدود الأخطاء (Thresholds) بناءً على طبيعة الخدمة وحجم الضغط المتوقع. لا تبالغ في الحماية لدرجة تعطيل النظام بدون داعي، ولا تتجاهل الأخطاء لدرجة الانهيار.
6.2 نمط الحواجز (Bulkhead Pattern): منع انتشار الضرر 🚢
مستوحى من تصميم السفن. بيقسموا الهيكل لمقصورات معزولة عشان لو ثُقبت مقصورة واحدة، السفينة ما تغرقش كلها.
التطبيق البرمجي
عزل موارد النظام (Thread Pools, Connection Pools) لكل خدمة على حدة.
المثال
خصص 50 Thread لخدمة “البحث” و 50 Thread لخدمة “إتمام الشراء”. لو انهارت خدمة البحث واستهلكت كل مواردها بسبب ضغط عالي أو خطأ برمجي، رح تضل خدمة الشراء شغالة بكفاءة لأن مواردها معزولة ومحفوظة. بيمنع هذا النمط انتشار الضرر عبر النظام.
مثال كود (Java باستخدام ExecutorService):
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BulkheadExample {
private static final int SEARCH_THREAD_POOL_SIZE = 50;
private static final int CHECKOUT_THREAD_POOL_SIZE = 50;
private static final ExecutorService searchExecutor = Executors.newFixedThreadPool(SEARCH_THREAD_POOL_SIZE);
private static final ExecutorService checkoutExecutor = Executors.newFixedThreadPool(CHECKOUT_THREAD_POOL_SIZE);
public static void main(String[] args) {
// Submit tasks to each thread pool
for (int i = 0; i < 100; i++) {
int taskNumber = i;
if (i % 2 == 0) {
searchExecutor.submit(() -> {
System.out.println("Search task " + taskNumber + " running in thread: " + Thread.currentThread().getName());
// Simulate a long-running task or potential failure
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
} else {
checkoutExecutor.submit(() -> {
System.out.println("Checkout task " + taskNumber + " running in thread: " + Thread.currentThread().getName());
// Simulate a checkout process
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
// Shutdown the executors when done
searchExecutor.shutdown();
checkoutExecutor.shutdown();
}
}
نصيحة من أبو عمر: راقب استخدام الموارد لكل حاجز (Bulkhead) بشكل دوري. لو لقيت خدمة بتستهلك موارد بشكل غير طبيعي، هذا ممكن يكون مؤشر على مشكلة لازم تحلها قبل ما تأثر على باقي النظام.
6.3 نمط الملحمة (Saga Pattern): المعاملات الموزعة في عالم الخدمات المصغرة 🧩
في بنية الخدمات المصغرة (Microservices)، ما بنقدر نستخدم معاملات ACID التقليدية اللي بتمتد عبر قواعد بيانات متعددة. تخيل بدك تحجز فندق وتشتري تذكرة طيران، وكل عملية موجودة في خدمة منفصلة.
الحل
Saga بتقسم المعاملة الكبيرة لسلسلة من المعاملات المحلية المتتابعة. كل خدمة بتنفذ جزئها وبتنشر حدث (Event) لتفعيل الخطوة التالية.
التعويض (Compensation)
التحدي الأكبر هو التراجع (Rollback). إذا فشلت الخطوة الخامسة في السلسلة، لازم نشغل “معاملات تعويضية” للتراجع عن الخطوات الأربعة السابقة (مثلاً: عملية “إعادة رصيد” بتلغي عملية “خصم رصيد” سابقة).
أنواع التنسيق
- Choreography: كل خدمة بتعرف شو تعمل وبتتفاعل مع الأحداث (لامركزي). زي فرقة موسيقية كل واحد بيعزف لحاله بس النتيجة بتكون متناسقة.
- Orchestration: فيه منسق مركزي (Orchestrator) بيوجه الخدمات لتنفيذ الخطوات (مركزي). زي قائد الأوركسترا.
مثال توضيحي (تبسيط للمفهوم):
تخيل عملية حجز سيارة:
- خدمة الحجز (Booking Service) تحجز السيارة وتنشر حدث “تم حجز السيارة”.
- خدمة الدفع (Payment Service) تخصم المبلغ وتنشر حدث “تم الدفع”.
- إذا فشل الدفع، تنشر خدمة الدفع حدث “فشل الدفع”.
- تستقبل خدمة الحجز حدث “فشل الدفع” وتقوم بإلغاء الحجز (معاملة تعويضية).
نصيحة من أبو عمر: تصميم المعاملات التعويضية أصعب من تصميم المعاملات الأصلية. لازم تتأكد إنها بتشتغل بشكل صحيح في جميع الحالات، حتى لو فشلت هي كمان! استخدم نظام تتبع مركزي (Tracing) لمراقبة سير العمليات في الـ Saga وتحديد المشاكل بسرعة.
الخلاصة: لا تخاف من الفشل، تعلم منه 💪
أنماط الاستقرار والمرونة مش مجرد حلول تقنية، هي عقلية. لازم نتقبل إنو الفشل جزء من الحياة، ونسعى لبناء أنظمة بتتعافى بسرعة وبتحمي المستخدمين من أي تجربة سيئة.
نصيحة أخيرة: ابدأ بتطبيق هاي الأنماط على الخدمات الأكثر أهمية في نظامك. راقب الأداء، وتعلم من الأخطاء، وحسن باستمرار. بالتوفيق!