Rate limiting و throttling في JavaScript: الحماية والأداء
\n\nهل تساءلت يومًا لماذا بعد عشرات النقرات السريعة على زر "إرسال" تتوقف خدمتك المفضلة فجأة عن الاستجابة؟ أو لماذا تطلب منك بعض المواقع "الانتظار لمدة 30 ثانية" قبل إعادة إرسال النموذج؟ الإجابة تكمن في آليتين قويتين تنقذان الواجهة الخلفية من التحميل الزائد والمستخدمين من "الأخطاء": rate limiting و throttling.
\n\nفي هذه المقالة، سنشرح كيفية تطبيق هذه التقنيات باستخدام JavaScript الخالص و Node.js. ستتعرف على الفرق بين throttle و debounce، وكيفية كتابة rate limiter بسيط لـ API، ولماذا سيعمل موقعك بشكل أسرع. هيا بنا!
\n\nما هو rate limiting ولماذا هو مطلوب؟
\n\nRate limiting (تحديد سرعة الطلبات) هو آلية حماية تحد من عدد الطلبات من العميل (أو عنوان IP) خلال فترة زمنية محددة. تخيل حاجزًا: يمكن للسيارة المرور، ولكن مرة واحدة فقط كل 10 ثوانٍ.
\n\nأين يتم تطبيق ذلك؟
\n- \n
- حماية API من هجمات DDoS والبريد العشوائي. \n
- منع التحميل الزائد على الخادم عند الطلبات الجماعية. \n
- التحكم في استخدام الموارد في APIs المدفوعة (مثل 100 طلب في الساعة). \n
- الحماية من أخطاء "الحلقة اللانهائية" على العميل. \n
على عكس throttling الذي يعمل على تنعيم تدفق الطلبات، فإن rate limiting هو جدار صلب: "توقف، لقد تجاوزت الحد".
\n\nThrottling مقابل Debounce: الفرق الرئيسي
\n\nغالبًا ما يتم الخلط بين throttling و debounce. دعنا نوضح الأمور.
\n\n- \n
- Throttle — يضمن عدم استدعاء الدالة أكثر من مرة واحدة كل N ميلي ثانية. تخيل صنبور ماء: تفتحه، ويتدفق الماء بسرعة ثابتة، حتى لو قمت بتحريك المقبض بشكل أسرع. \n
- Debounce — يؤجل استدعاء الدالة حتى تمر N ميلي ثانية بعد آخر حدث. مثالي للبحث مع الإكمال التلقائي: أثناء كتابة المستخدم، لا يتم إرسال الطلب. \n
في سياق rate limiting، نحن مهتمون بـ throttle — نريد التحكم في التردد، وليس تأجيل الطلبات.
\n\nأبسط rate limiter في JavaScript
\n\nلنكتب تطبيقنا الخاص لـ rate limiter في Node.js. سيتذكر وقت آخر طلب من كل مستخدم ويمنع الطلبات المتكررة جدًا.
\n\nclass RateLimiter {\n constructor(limit, windowMs) {\n this.limit = limit; // الحد الأقصى للطلبات\n this.windowMs = windowMs; // النافذة الزمنية بالميلي ثانية\n this.requests = new Map(); // مخزن: userId -> [timestamps]\n }\n\n allow(userId) {\n const now = Date.now();\n const userRequests = this.requests.get(userId) || [];\n \n // حذف الإدخالات القديمة\n const recentRequests = userRequests.filter(\n ts => now - ts < this.windowMs\n );\n \n if (recentRequests.length >= this.limit) {\n console.log(`Rate limit exceeded for ${userId}`);\n return false;\n }\n \n recentRequests.push(now);\n this.requests.set(userId, recentRequests);\n return true;\n }\n}\n\n// الاستخدام\nconst limiter = new RateLimiter(5, 60000); // 5 طلبات في الدقيقة\n\n// محاكاة الطلبات\nfor (let i = 0; i < 10; i++) {\n setTimeout(() => {\n const allowed = limiter.allow('user123');\n console.log(`Request ${i+1}: ${allowed ? '✅ مسموح' : '❌ محظور'}`);\n }, i * 2000); // كل ثانيتين\n}\n\nهذا الكود مناسب للأغراض التعليمية، ولكن في الإنتاج من الأفضل استخدام حلول جاهزة (مثل express-rate-limit لـ Express أو rate-limiter-flexible).
Throttling على العميل: الحماية من النقرات السريعة
\n\nغالبًا ما تنشأ المشكلة على جانب العميل: يضغط المستخدم على زر "إرسال" عدة مرات، وتُرسل الطلبات بشكل متوازٍ. ر