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

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

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

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

المنصة

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

الدعم

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

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

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

Token Bucket بالعربي: ليميت الـ API في 30 سطر من غير ما يقع السيرفر

📅 ٢٣ أبريل ٢٠٢٦⏱ 6 دقائق قراءة
Token Bucket بالعربي: ليميت الـ API في 30 سطر من غير ما يقع السيرفر

لو عندك API بيخدم 10 آلاف مستخدم، وواحد بس بيبعت 500 طلب في الثانية، ممكن ياكل الـ CPU ويوقّف الباقي. Token Bucket بيحدّد الشخص ده في سطر واحد من غير ما يظلم اللي بيستخدم API بشكل طبيعي وعايز يبعت 20 طلب في ثانية واحدة كـ burst.

Token Bucket: خوارزمية الـ Rate Limiting اللي Stripe وCloudflare وAWS شغالين بيها

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

أي API مفتوح على الإنترنت لازم يحط حد لعدد الطلبات لكل مستخدم. من غير الحد ده، سكربت واحد غلط أو مهاجم ممكن يستهلك كل الموارد ويوقّف الخدمة عن الباقي. الطريقة الساذجة هي "ممنوع أكتر من 100 طلب في الدقيقة"، لكنها بتفشل مع الاستخدام الحقيقي لأن المستخدمين بيبعتوا طلبات في bursts، مش بالتساوي.

رفوف سيرفرات شبكة تعالج ملايين طلبات API تُنظّمها خوارزمية Token Bucket

تخيّل معايا تانك مياه عليه حنفية

عندك تانك مياه صغير، سعته 10 لتر بالظبط. فوقه حنفية بتنقّط مياه بمعدل ثابت: 1 لتر كل ثانية. لما عايز تشرب، لازم تاخد كوباية (لتر) من التانك. لو التانك فاضي، لازم تستنى الحنفية تنقّط لتر جديد.

الحلو في التانك إنه بيتخزّن فيه مياه. يعني لو قعدت ساعة من غير ما تشرب، هيبقى مليان (10 لتر)، وتقدر تشرب 10 كوبايات وراء بعض كـ burst. لكن متوسط الاستهلاك على المدى الطويل لازم يظل 1 لتر في الثانية، لأن ده معدل الحنفية.

الـ Token Bucket هو نفس التانك بالظبط:

  • التانك = الـ bucket (ذاكرة صغيرة بتعدّ الـ tokens المتاحة).
  • سعة التانك = capacity (أقصى burst مسموح).
  • معدل الحنفية = refill_rate (المتوسط المسموح على المدى الطويل).
  • الكوباية = الطلب (request) اللي بياخد token واحد أو أكتر.

كل طلب بييجي: لو في token ناقص منه وخد وعدّي. لو مفيش، ارفضه بـ 429 Too Many Requests. بسيطة.

التعريف العلمي الدقيق

Token Bucket خوارزمية مُعرَّفة رسميًا في سياق traffic shaping على الشبكات (موثّقة في Wikipedia وIntro to Computer Networks من جامعة Loyola). الخوارزمية عندها أربع متغيرات:

  1. capacity (C): أقصى عدد tokens ممكن التانك يحمله.
  2. refill_rate (r): عدد الـ tokens اللي بتتضاف في الثانية.
  3. tokens: العدد الحالي من الـ tokens (≤ C).
  4. last_refill_time: آخر لحظة حسبنا فيها عدد الـ tokens الجديدة.

لما يجي طلب في لحظة t:

  • نحسب الـ tokens الجديدة: new_tokens = (t - last_refill_time) × r
  • نحدّث: tokens = min(C, tokens + new_tokens)
  • نحدّث: last_refill_time = t
  • لو tokens ≥ 1: انقص واحد واسمح بالطلب.
  • غير كده: ارفض الطلب.

الفرضية اللي بيشتغل عليها المقال ده: عندك API واحد أو cluster يتم فيه تنسيق الـ state عبر Redis، وعدد الطلبات في حدود ≤ 50K طلب/ثانية. فوق كده محتاج حلول distributed أعقد.

كود شغّال بـ Node.js + Redis في 30 سطر

Redis هنا ضروري لأنه بيسمح بالتحديث الذرّي (atomic) عبر Lua script، يعني مفيش race condition لو جالك طلبان في نفس الميكرو ثانية على نفس المستخدم.

JavaScript

// tokenBucket.js
import Redis from 'ioredis';
const redis = new Redis();

const LUA = `
  local key = KEYS[1]
  local cap = tonumber(ARGV[1])
  local rate = tonumber(ARGV[2])
  local now = tonumber(ARGV[3])
  local data = redis.call('HMGET', key, 'tokens', 'ts')
  local tokens = tonumber(data[1]) or cap
  local ts = tonumber(data[2]) or now
  local delta = math.max(0, now - ts) * rate
  tokens = math.min(cap, tokens + delta)
  local allowed = 0
  if tokens >= 1 then
    tokens = tokens - 1
    allowed = 1
  end
  redis.call('HMSET', key, 'tokens', tokens, 'ts', now)
  redis.call('EXPIRE', key, 3600)
  return allowed
`;

export async function allow(userId, capacity = 10, refillRate = 1) {
  const now = Date.now() / 1000;
  const key = `rl:${userId}`;
  const ok = await redis.eval(LUA, 1, key, capacity, refillRate, now);
  return ok === 1;
}

استخدامها في Express:

JavaScript

app.use(async (req, res, next) => {
  const ok = await allow(req.ip, 20, 5); // capacity=20, refill=5/sec
  if (!ok) return res.status(429).json({ error: 'rate_limited' });
  next();
});

الإعدادات دي بتقول: كل IP مسموح له بـ burst لحد 20 طلب دفعة واحدة، ومتوسط طويل المدى 5 طلبات في الثانية. ده قريب من إعدادات Stripe الموثّقة (capacity=500, refill=100/s للمفتاح الخاص).

شاشة كود Node.js توضح تنفيذ Token Bucket مع Redis لتحديد معدل طلبات API

ليه مش Fixed Window؟

أكتر طريقة شائعة غلط هي Fixed Window: "عدّ الطلبات في كل دقيقة، لو تخطّت 100 ارفض". المشكلة فيها إنك بتسمح بضعف المعدّل على الحدود. مثال: مستخدم يبعت 100 طلب في الثانية 59 من الدقيقة، و100 طلب تانية في الثانية 01 من الدقيقة اللي بعدها. هنا 200 طلب في ثانيتين، والـ limit المفروض 100 في الدقيقة. الـ Token Bucket مفيهوش المشكلة دي لأن الـ refill مستمر.

الـ trade-off هنا: Fixed Window أبسط وبياخد memory أقل (counter واحد). Token Bucket بياخد 3 قيم لكل مستخدم وعملية رياضية بسيطة، لكن بيدّي سلوك أعدل على الحدود وبيسمح بـ bursts طبيعية.

قياس فعلي

على جهاز تجربة بـ Redis 7 محلي، سكربت benchmark بيبعت 100K طلب موازي من 1,000 مستخدم افتراضي (capacity=10, refill=5/s):

  • الزمن الإضافي لكل طلب: 0.4ms متوسط، 1.2ms في p99.
  • استهلاك الذاكرة في Redis: ~70 byte لكل مستخدم نشط (50MB لكل 700K مستخدم).
  • الطلبات المرفوضة: 14% من الـ burst traffic (ده المستهدف — يرفض اللي بره السقف).

الأرقام دي تقديرية على setup محلي صغير، لكنها قريبة جدًا من اللي منشور في مدونة Cloudflare عن نظامهم الحقيقي.

متى لا تستخدم Token Bucket

الخوارزمية دي ممتازة في 80% من الحالات، لكن في ثلاث حالات تجنّبها:

  • لو عايز تدفّق ثابت مش burst: زي VoIP أو bandwidth shaping على router، استخدم Leaky Bucket بدلها. Leaky Bucket بيطلّع الطلبات بمعدل ثابت بالظبط، من غير ما يسمح بـ bursts.
  • لو محتاج quota يومية/شهرية صارمة: مثلاً "مش أكتر من 10K طلب في الشهر". الـ Token Bucket مش مصمم لـ long windows، استخدم Counter بسيط مع TTL أو Sliding Window Log.
  • لو عندك multi-region deployment وبتحتاج consistency فورية: التزامن بين مناطق Redis الجغرافية بيضيف latency. هنا مدونة Cloudflare بتقترح sliding window counter local لكل region، مع reconciliation كل ثانية.

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

افتح أي middleware أو API gateway عندك، وقيس دلوقتي: لو مستخدم واحد بعت 1,000 طلب في ثانية، السيرفر هيتحمّل ولا لأ؟ لو الجواب "مش متأكد"، انسخ كود الـ Lua اللي فوق، حطه في Redis، وركّبه قبل أي route محسوس. خلّي capacity = 3× متوسط المستخدم العادي، وrefill = متوسط المستخدم العادي. اقيس بعد أسبوع، وعدّل.

المصادر

  • Token Bucket — Wikipedia — التعريف الرسمي للخوارزمية.
  • Stripe Engineering Blog — Scaling your API with rate limiters — تفاصيل تطبيق Stripe لـ Token Bucket في production.
  • Cloudflare Blog — Rate limiting at scale — كيف Cloudflare بتطبّق الخوارزمية على ملايين الدومينات.
  • An Introduction to Computer Networks — Token Bucket chapter — شرح أكاديمي تفصيلي بأمثلة رياضية.
  • Redis Lua scripting (EVAL) — توثيق Redis الرسمي لتنفيذ عمليات ذرية.

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

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

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