إزاي تحمي السيرفر بتاعك؟ دليل بناء الـ Distributed Rate Limiting باستخدام Redis
أهلاً بيك يا فنان في عالم الـ Backend! تخيل معايا السيناريو ده: لسة منزل السيرفر بتاعك على الإنتاج (Production)، ولقيت مرة واحدة Traffic رهيب جاي على الـ API. السيرفر بدأ يهنج، والـ Database جابت آخرها، والسايت وقع! لما فتحت اللوجات (Logs)، لقيت إن في سكريپت خبيث أو بوت (Bot) بيضرب طلبات بالهبل (Requests) عشان يوقع السystem، أو مستخدم بيعمل تحديث للصفحة كل ثانية. هنا بقى بيجي دور حارس السيرفر الأمين: تحديد معدل الطلبات أو الـ Rate Limiting.
الموضوع بيكون سهل وبسيط لو عندك سيرفر واحد (Single Server)، بتسجل الطلبات في الـ Memory وخلاص. بس الإثارة الحقيقية بتبدأ لما السيستم بتاعك يكبر وتكون شغال في بيئة موزعة (Distributed System) ووراك كذا سيرفر شغالين وراء Load Balancer. هنا الـ In-memory مش هي ينفعك، ولازم نحل المشكلة دي بطريقة مركزية سريعة. وهنا بيظهر بطل قصتنا النهارده: الـ Redis!
Table of contents [Show]
إيه هو الـ Distributed Rate Limiting وليه بنحتاجه؟
باختصار كده، الـ Rate Limiting هو عملية تحكم في عدد المرات اللي يقدر فيها المستخدم أو الـ IP يبعت طلب للـ API في فترة زمنية معينة (زي مثلاً: 100 طلب في الدقيقة). أما الـ Distributed Rate Limiting، فهو تطبيق نفس الفكرة بس على نظام متوزع على أكتر من سيرفر (Multiple Servers).
لو عندك 3 سيرفرات وراء بعض، والـ Load Balancer بيوزع الطلبات عليهم عشوائي، مستخدم ممكن يبعت 5 طلبات للسيرفر الأول، و5 للسيرفر التاني، وهو مسموح له بـ 10 طلبات بس في الإجمالي. لو كل سيرفر بيحسب لوحده، المستخدم ده هيقدر يكسر الـ Limit ويبعت أضعاف الطلبات اللي مسموح بيها! عشان كده لازم يكون فيه مخزن بيانات مركزي (Centralized Data Store) سريع جداً، وكل السيرفرات تسأل عليه قبل ما تنفذ أي طلب، وهنا مفيش أسرع ولا أنسب من Redis للمهمة دي.
الخوارزميات المشهورة لتنفيذ الـ Rate Limiting
قبل ما ندخل في الكود، لازم نفهم التكات والأساليب اللي بنحسب بيها الطلبات. في كذا خوارزمية مشهورة (Algorithms)، تعال نعرف أشهر طريقتين بنستخدمهم مع الـ Redis:
- خوارزمية النافذة الثابتة (Fixed Window Counter): الأسهل والأشهر. بنقسم الوقت لنافذة ثابتة (مثلاً كل دقيقة تبدأ من الصفر). بنخزن عداد للمستخدم في الـ Redis ونزوده مع كل طلب ونحطله وقت انتهاء (TTL) بدقيقة. عيبها الوحيد إن المستخدم ممكن يضرب الـ Limit كله في آخر ثانيتين من الدقيقة القديمة، ويبعت زيهم أول ثانيتين من الدقيقة الجديدة، فيبقى كأنه خد ضعف العداد في وقت قصير.
- خوارزمية نافذة الانزلاق باستخدام الـ Sorted Sets (Sliding Window Log): الأقوى والأكثر دقة. بنخزن وقت كل طلب عمله المستخدم جوه Sorted Set في الـ Redis. لما بيجي طلب جديد، بنمسح الطلبات القديمة اللي عدى عليها أكتر من الدقيقة، ونعد الطلبات الباقية. لو أقل من المسموح، بنسجل الطلب الجديد. الدقة هنا 100% بس بتستهلك مساحة أكتر شوية.
تطبيق عملي: بناء Rate Limiter بـ Node.js و Redis
تعال نكتب كود عملي يوضح الفكرة باستخدام Redis و Node.js مع مكتبة ioredis. هنستخدم خوارزمية الـ Fixed Window عن طريق أمر INCR و EXPIRE.
const Redis = require('ioredis');
const redis = new Redis(); // بيوصل على الـ localhost افتراضياً
async function rateLimiter(userId, limit = 5, windowSeconds = 60) {
const key = `rate:limit:${userId}`;
// بنزود العداد وبنرجع القيمة الجديدة
const requestsCount = await redis.incr(key);
// لو ده أول طلب في النافذة الزمنية، بنحط TTL للـ Key
if (requestsCount === 1) {
await redis.expire(key, windowSeconds);
}
// بنقارن العداد بالحد المسموح
if (requestsCount > limit) {
return {
allowed: false,
message: 'هدّي اللعب يا فنان! عدیت الحد المسموح من الطلبات.',
currentRequests: requestsCount
};
}
return {
allowed: true,
currentRequests: requestsCount
};
}
الكود اللي فات ده ذكي جداً وعملي، لأن عمليات Redis زي INCR و EXPIRE بتتم بشكل ذري (Atomic)، يعني مفيش أي فرصة يحصل Race Condition حتى لو ألف سيرفر كلموا الـ Redis في نفس الميلي ثانية.
إزاي تحسن الأداء (Optimization) وتتجنب المشاكل في الإنتاج؟
عشان السيستم بتاعك يستحمل ضغط عالي (High Conformance)، في كم نقطة لازم تاخد بالك منهم وأنت شغال:
- استخدام Redis Cluster: لو الـ Traffic عندك مرعب، سيرفر Redis واحد هياخد وقت في المعالجة. الحل هنا إنك تعمل Redis Cluster وتوزع الـ Keys على أكتر منو دة (Nodes).
- تجنب الـ Network Latency: حاول تحط سيرفر الـ Redis في نفس الـ Data Center اللي فيها الـ Backend Servers بتاعتك عشان تقلل زمن الاستجابة (Latency) على قد ما تقدر.
- الـ Lua Scripts: لو بتنفذ خوارزمية معقدة زي الـ Sliding Window وتحتاج أكتر من أمر Redis يتنفذوا مع بعض بدون تداخل، استخدم Lua Scripting داخل الـ Redis لأنها بتتنفذ كـ Transaction واحدة وسريعة جداً.
نصيحة من أخ لتطوير مهاراتك
الموضوع في الأول ممكن تحسه مكلكع شوية، بس نصيحتي ليك إنك متقراش الكلام وتعدي. افتح اللابتوب بتاعك، واعمل بروجكت بسيط بـ Express.js و Docker و Redis، وجرب تباصي مليون طلب في الثانية وتشوف الـ Rate Limiter وهو بيصد الهجوم. العمل بايديك هو اللي بيثبت المعلومة وبيخليك فاهم "الحتت الصعبة" اللي بتفرق بين المبرمج العادي والمبرمج المحترف. بالتوفيق يا وحش الكودينج!