ذكريات من غزّة: عندما يصبح الصمود ضرورة
بتذكر مرة، كنا شغالين على تطبيق لشركة توزيع كهرباء في غزّة. الوضع كان صعب، الكهربا بتقطع باليوم 12 ساعة، والنت أبطأ من سلحفاة ماشية بالرمل. تخيل إنك بدك تعمل نظام فوترة وتحصيل فواتير لناس عايشة بظروف زي هيك. الفشل مش مجرد احتمال، الفشل جزء من الروتين اليومي. هون فهمت عن جد شو يعني “الصمود” في البرمجيات. مش بس منع المشاكل، لكن القدرة على الاستمرار حتى لو كل شي حواليك بينهار. من هاديك التجربة، صرت أركز على أنماط الصمود في كل مشروع بشتغله، وخصوصًا في بيئات Node.js غير المتزامنة، لأنها بتتعامل مع كتير طلبات بنفس الوقت، وأي فشل بسيط ممكن يعمل كارثة.
أنماط الصمود: التصميم للفشل الحتمي
في الأنظمة الموزعة وبيئات السحابة، الفشل ليس احتمالاً، بل هو حقيقة مؤكدة. الخدمات ستتوقف، الشبكات ستتباطأ، وقواعد البيانات ستواجه ضغطاً. الهندسة الجيدة لا تعني منع الفشل، بل تعني بناء أنظمة قادرة على الصمود والتعافي بذكاء عند حدوثه. في بيئة Node.js غير المتزامنة، تصبح هذه الأنماط أكثر أهمية لمنع تراكم الطلبات المعلقة الذي يؤدي لاستنفاد الموارد.
قاطع الدائرة (Circuit Breaker): خط الدفاع الأول
نمط قاطع الدائرة هو خط الدفاع الأول ضد “الفشل المتسلسل” (Cascading Failures). تخيل أن خدمتك تعتمد على واجهة برمجة تطبيقات خارجية للدفع، وهذه الواجهة توقفت عن العمل. بدون قاطع دائرة، سيستمر تطبيقك في إرسال الطلبات وانتظار الردود حتى انتهاء المهلة (Timeout). مع تراكم آلاف الطلبات المنتظرة، ستنفد موارد الخادم الخاص بك وسيتوقف عن العمل تماماً.
يعمل قاطع الدائرة (مثل مكتبة opossum في Node.js) بمراقبة معدلات الفشل. إذا تجاوزت عتبة معينة (مثلاً 50%)، ينتقل القاطع إلى حالة “مفتوح” (Open)، حيث يتم رفض أي طلبات جديدة فوراً دون محاولة الاتصال بالخدمة المعطلة، مما يوفر الموارد ويعطي الخدمة الخارجية فرصة للتعافي. بعد فترة، يسمح بمرور طلبات تجريبية (Half-Open) للتأكد من عودة الخدمة.
const Opossum = require('opossum');
const options = {
timeout: 3000, // المهلة بالمللي ثانية
errorThresholdPercentage: 50, // نسبة الخطأ التي تؤدي لفتح الدائرة
resetTimeout: 10000 // المدة الزمنية قبل محاولة إعادة فتح الدائرة
};
const circuit = new Opossum(externalApiCall, options);
circuit.on('open', () => console.log('Circuit OPEN'));
circuit.on('halfOpen', () => console.log('Circuit HALF_OPEN'));
circuit.on('close', () => console.log('Circuit CLOSED'));
async function externalApiCall() {
// استدعاء واجهة برمجة التطبيقات الخارجية
// ...
}
// استخدام قاطع الدائرة
circuit.fire()
.then(result => console.log('Result:', result))
.catch(err => console.error('Error:', err));
نصيحة: ابدأ بتطبيق قاطع الدائرة على أهم الخدمات الخارجية التي يعتمد عليها تطبيقك. راقب أداء قاطع الدائرة وعدّل الإعدادات بناءً على سلوك النظام.
إعادة المحاولة الذكية (Smart Retries): ليست كل المحاولات متساوية
الفشل العابر (Transient Failure) مثل انقطاع الشبكة للحظات أمر شائع. نمط “إعادة المحاولة” (Retry) يمكن أن يحل هذه المشكلة، ولكن التطبيق الساذج له قد يكون مدمراً. إذا فشلت خدمة وقام جميع العملاء بإعادة المحاولة فوراً وفي نفس الوقت، فإنهم يتسببون في “عاصفة” (Retry Storm) تقضي على أي فرصة لتعافي الخدمة.
الحل يكمن في “التراجع الأسي” (Exponential Backoff) – الانتظار لثانية، ثم ثانيتين، ثم أربع – مع إضافة “تشتيت عشوائي” (Jitter) لضمان عدم تزامن طلبات العملاء.
const axios = require('axios');
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function retryRequest(url, maxRetries = 3) {
for (let i = 0; i console.log('Success:', response.data))
.catch(error => console.error('Final error:', error));
نصيحة: استخدم مكتبات جاهزة تدعم التراجع الأسي والتشتيت العشوائي، مثل retry، بدلاً من كتابة الكود من الصفر.
نمط الحواجز (Bulkhead Pattern): لا تضع كل البيض في سلة واحدة
مستوحى من تصميم السفن، حيث يتم تقسيم الهيكل إلى مقصورات معزولة حتى لا تغرق السفينة بالكامل إذا ثقبت مقصورة واحدة. في البرمجيات، يعني هذا عزل موارد النظام المخصصة لخدمات مختلفة. لا ينبغي لخدمة “إرسال التقارير” البطيئة أن تستهلك كل اتصالات قاعدة البيانات وتمنع خدمة “تسجيل الدخول” السريعة من العمل.
يمكن تطبيق ذلك في Node.js عبر تحديد حدود للتزامن (Concurrency Limits) لكل مسار أو وظيفة، أو استخدام مسابح خيوط/اتصالات منفصلة للمهام المختلفة.
const express = require('express');
const app = express();
const asyncMiddleware = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next);
};
const MAX_CONCURRENT_REQUESTS = 10;
let currentRequests = 0;
const bulkheadMiddleware = (req, res, next) => {
if (currentRequests >= MAX_CONCURRENT_REQUESTS) {
return res.status(503).send('Service Unavailable: Too many requests');
}
currentRequests++;
res.on('finish', () => {
currentRequests--;
});
next();
};
app.get('/heavy-task', bulkheadMiddleware, asyncMiddleware(async (req, res) => {
// Simulate a long-running task
await new Promise(resolve => setTimeout(resolve, 5000));
res.send('Heavy task completed!');
}));
app.get('/light-task', (req, res) => {
res.send('Light task completed!');
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
نصيحة: استخدم حاويات Docker لتطبيق الحواجز على مستوى أعمق، حيث يمكن تحديد حدود الموارد (CPU, Memory) لكل خدمة.
الفشل السريع (Fail Fast) والبدائل (Fallbacks): لا تدع المستخدم ينتظر إلى الأبد
أفضل استجابة للنظام المشغول هي الرفض السريع. بدلاً من جعل المستخدم ينتظر 30 ثانية لرؤية رسالة خطأ، يجب أن يفشل النظام فوراً إذا كانت طوابير الانتظار ممتلئة. بالتوازي، يجب توفير “بدائل” (Fallbacks). إذا فشلت خدمة التوصيات الشخصية، لا تعرض صفحة فارغة، بل اعرض قائمة “الأكثر شيوعاً” المجهزة مسبقاً في الكاش. هذا يحافظ على تجربة المستخدم حتى في أوقات الأزمات.
async function getRecommendations(userId) {
try {
const recommendations = await fetchRecommendationsFromApi(userId);
return recommendations;
} catch (error) {
console.error('Failed to fetch recommendations, using fallback:', error);
return getPopularProductsFromCache(); // Fallback to popular products
}
}
async function fetchRecommendationsFromApi(userId) {
// Simulating API call
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() console.log('Recommendations:', recommendations));
نصيحة: قم بتخزين البيانات البديلة (Fallbacks) في ذاكرة التخزين المؤقت (Cache) لضمان سرعة الوصول إليها في حالات الفشل.
الخلاصة: الصمود مفتاح الاستمرارية 🔑
أنماط الصمود ليست مجرد حلول تقنية، بل هي فلسفة تصميم. هي طريقة تفكير بتخليك تتوقع الأسوأ وتستعد له. في بيئة Node.js، تطبيق هذه الأنماط بيضمنلك نظام مستقر وقادر على التعامل مع الضغط والأعطال، وبيحافظ على تجربة المستخدم سلسة حتى في أسوأ الظروف. تذكر دائمًا: الفشل وارد، لكن الاستسلام ليس خيارًا.💪