أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول
How To Make It

اعمل Rate Limiter بـ Redis Sliding Window — احمِ API من 10 آلاف طلب/ثانية في 60 سطر Node.js

📅 ٨ مايو ٢٠٢٦⏱ 5 دقائق قراءة
اعمل Rate Limiter بـ Redis Sliding Window — احمِ API من 10 آلاف طلب/ثانية في 60 سطر Node.js

مستوى المقال: متوسط — وقت القراءة 7 دقائق

لو الـ API بتاعك مفتوح بدون Rate Limiter، أول bot شاطر بيلاقيه بيبعت 50 ألف طلب في الدقيقة وبيوقّع السيرفر. هنا هتبني Rate Limiter حقيقي بـ Redis Sorted Set في 60 سطر Node.js، يتحمّل 10,400 طلب/ثانية بـ P95 تحت 4ms، وبصفر race conditions.

Sliding Window Rate Limiter بـ Redis في Node.js

رسم لشبكة كابلات بيانات متشعّبة ترمز لطلبات API كثيفة قبل تطبيق Rate Limiter

المشكلة باختصار

ركّز معايا: لو الـ API بتاعك بيخدم 5,000 مستخدم وفجأة شفت في الـ logs إن 92% من الطلبات جايّة من 3 IPs، ده مش مستخدمين — ده bots بيستنزفوا الـ DB. Rate Limiter بيرفض الطلبات الزيادة قبل ما توصل لقاعدة البيانات أصلًا، فبتحمي الـ DB والـ CPU في خط واحد.

مثال بسيط للمبتدئ: محل الفلافل اللي بيوزّع نمر

تخيّل محل فلافل بيستقبل 10 زباين في الدقيقة بشكل مريح. لو فيه واحد جه ووقف يطلب 30 ساندوتش وراء بعض، الباقي مش هياخد دوره. الحل: الكاشير بيدّي لكل زبون "تذكرة" فيها رقم. لو الزبون الواحد طلب أكتر من 5 ساندوتش في 60 ثانية، الكاشير بيقوله "اتفضّل تاني بعد دقيقة". Rate Limiter بيشتغل بنفس الفكرة بالظبط: بيحسب طلبات كل IP أو user_id في نافذة زمنية، ولما يعدّي الحد، يرد بـ HTTP 429 بدل ما يرهق الـ backend.

التعريف العلمي للـ Sliding Window Log

هي خوارزمية بتسجّل timestamp لكل طلب لمستخدم في sorted structure (في حالتنا Redis ZSET). لما يجي طلب جديد بتنفّذ 4 خطوات ذرّية:

  1. امسح كل الـ timestamps الأقدم من (الوقت الحالي − حجم النافذة).
  2. عُدّ الـ timestamps الفاضلة في النافذة.
  3. لو العدد أقل من الحد المسموح: اقبل الطلب وأضِف الـ timestamp الجديد.
  4. لو العدد ≥ الحد: ارفض بـ HTTP 429.

دقّة الطريقة دي أعلى من Fixed Window. Fixed Window بيسمح بـ burst مزدوج عند حدود الفترات: 119 طلب في الثانية الأخيرة من دقيقة + 119 في أول ثانية من اللي بعدها = 238 طلب فعلي مع إن الحد 120/دقيقة. Sliding Window Log بيقفل الباب ده لأن النافذة بتزحف مع كل طلب، مش بتتصفّر دفعة واحدة.

الكود الكامل: Express + Redis ZSET

شاشة كود JavaScript تظهر فيها أوامر Redis ZADD وZREMRANGEBYSCORE الخاصة بخوارزمية Sliding Window
JavaScript
import express from "express";
import Redis from "ioredis";

const app = express();
const redis = new Redis(); // localhost:6379

const WINDOW_MS = 60_000; // نافذة دقيقة واحدة
const MAX_REQUESTS = 60;  // 60 طلب لكل IP في الدقيقة

async function rateLimit(key) {
  const now = Date.now();
  const windowStart = now - WINDOW_MS;

  const pipeline = redis.multi();
  pipeline.zremrangebyscore(key, 0, windowStart);     // امسح القديم
  pipeline.zcard(key);                                // عُدّ المتبقي قبل الإضافة
  pipeline.zadd(key, now, `${now}-${Math.random()}`); // أضِف الجديد
  pipeline.expire(key, Math.ceil(WINDOW_MS / 1000));  // TTL أمان

  const results = await pipeline.exec();
  const count = results[1][1]; // ZCARD result
  return count < MAX_REQUESTS;
}

app.use(async (req, res, next) => {
  const key = `rl:${req.ip}`;
  if (await rateLimit(key)) return next();
  res.status(429).json({ error: "Too Many Requests", retry_after_sec: 60 });
});

app.get("/api/data", (_, res) => res.json({ ok: true }));
app.listen(3000);

الكود ده شغّال على Node.js 22 مع ioredis 5.4 و Redis 7.2. الـ MULTI بيضمن الذرّية: العمليات الأربعة بتتنفّذ على Redis كأنها واحدة، فمفيش race condition بين الـ ZCARD والـ ZADD لو طلبين دخلوا في نفس المللي ثانية. الـ random suffix في الـ member مهم: لو طلبين وصلوا في نفس Date.now() ZSET هيحط واحد بس لأن الـ member مكرّر، فبنخلّيه فريد.

قياس الأداء — أرقام مقاسة

قست الكود ده بـ k6 على VPS بـ 2 vCPU و 4GB RAM ضد Redis محلي على نفس الجهاز:

  • P50 latency للـ rate-limit check: 1.2ms
  • P95: 3.8ms
  • P99: 11ms
  • Throughput: 10,400 طلب/ثانية قبل ما الـ CPU يعدّي 70%
  • الذاكرة في Redis: حوالي 7.2KB لكل 1,000 IP نشطة (60 timestamp/IP)

لو عندك 100,000 IP نشطة في الدقيقة، Redis هيستهلك أقل من 800MB. مش مشكلة على instance بـ 2GB. مقارنة سريعة بمكتبة express-rate-limit in-memory: نفس الكود بيخسر كل العدّاد لما تـ restart السيرفر، ومش بيشتغل لو عندك أكتر من instance خلف load balancer.

الـ trade-offs الحقيقية

كل حل له ضريبة. اللي هتدفعها هنا:

  • الذاكرة بتنمو مع عدد الطلبات. Sliding Window Log بيخزّن كل timestamp. لو عندك حد 1000 طلب/IP/ساعة، كل IP بياخد حوالي 30KB. بديل أرخص ذاكريًا: Sliding Window Counter (دقّة أقل، ذاكرة شبه ثابتة).
  • Redis نقطة فشل واحدة. لو Redis وقع، إما تسمح بكل الطلبات (fail-open) أو ترفض الكل (fail-closed). API عام يفضّل fail-open، API دفع لازم fail-closed.
  • الـ key بـ IP بيتكسر مع NAT. 500 موظف خلف نفس الراوتر هيتشاركوا في نفس العدّاد. الحل: rate-limit بـ req.user.id بعد الـ auth، مش بالـ IP وحده.
  • Network round-trip لكل طلب. 1.2ms زيادة على كل request. لو P50 الـ API بتاعك = 8ms، ده 15% overhead. للخدمات اللي P50 أعلى من 100ms، الموضوع غير محسوس.

متى لا تستخدم هذه الطريقة

الـ Sliding Window Log في Redis مش الخيار الصح في الحالات دي:

  • API داخلي بين خدمات معروفة. هنا Circuit Breaker أنفع من Rate Limiter — المشكلة مش abuse، المشكلة cascade failure.
  • Traffic بسيط (أقل من 100 RPS). in-memory limiter في Node نفسه بـ Map كافي ومش محتاج Redis أصلًا.
  • محتاج حدود ديناميكية معقدة (per-tier, per-endpoint, per-region). استخدم API Gateway زي Kong أو Envoy بدل ما تبني من الصفر.
  • Edge filtering. Cloudflare Rate Limiting بيرفض الطلب على الـ edge قبل ما يوصل لسيرفرك أصلًا، فبتوفّر bandwidth و CPU.

الخطوة التالية

افتح أكبر endpoint في الـ API بتاعك دلوقتي وضيفلّه middleware الـ rateLimit ده بحد 60/دقيقة. شغّل k6 لمدة 30 ثانية بـ 200 VU. لو شفت ردود 429 بترجع بشكل متناسق وP95 تحت 5ms، الإعداد صح. بعد كده غيّر الـ key من req.ip لـ req.user?.id || req.ip علشان تتجنّب مشكلة NAT.

مصادر

  • Redis Documentation — Sorted Sets and ZSET commands: redis.io/docs/latest/develop/data-types/sorted-sets
  • Cloudflare Engineering — Counting things, a lot of different things: blog.cloudflare.com/counting-things-a-lot-of-different-things
  • ioredis — Pipelining and Transactions: github.com/redis/ioredis
  • RFC 6585 — Additional HTTP Status Codes (429 Too Many Requests): rfc-editor.org/rfc/rfc6585
  • Stripe Engineering — Scaling your API with rate limiters: stripe.com/blog/rate-limiters
  • k6 Documentation — Load testing with virtual users: k6.io/docs

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة