الرئيسيةمن أناالدوراتالمدونةسوق الأوامرالمناهج والباقاتالشركاء

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

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

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • المناهج والباقات
  • سوق الأوامر
  • المدونة

الدعم

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

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

الرئيسيةالدوراتالمناهجالمدونةالدخول
DevOps بالعربي

تحديد معدل الطلبات على API بخوارزمية Token Bucket

متوسط31 يوليو 20265 دقائق قراءة
تحديد معدل الطلبات على API بخوارزمية Token Bucket

تحديد معدل الطلبات على API بخوارزمية Token Bucket

هذا المقال لمستوى: متوسط

في نهاية المقال هيبقى عندك سكربت واحد يمنع أي عميل من إغراق الـ API بتاعك، ويسمح في نفس الوقت بموجة طلبات قصيرة طبيعية. الطريقة الشائعة (عدّاد بسيط لكل ثانية) بتفشل عند حدود النافذة الزمنية، وهنشوف ليه، وهنحل المشكلة بخوارزمية دلو الرموز.

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

تخيّل بواب نادي بيوزّع تذاكر دخول. كل دقيقة بيحط 10 تذاكر في صندوق، والصندوق بيسع 10 بس. أي زائر عايز يدخل لازم ياخد تذكرة. لو الصندوق فاضي، الزائر يستنى. لو النادي هدأ شوية، التذاكر بتتراكم لحد سقف الـ 10، فتقدر مجموعة تدخل مرة واحدة من غير زحمة.

ده بالظبط اللي بيعمله تحديد المعدل (Rate Limiting) للـ API. الافتراض هنا إن عندك خدمة عليها ضغط حقيقي، وعميل واحد (أو IP واحد) ممكن يبعت طلبات أكتر من نصيبه العادل، فيخنق باقي المستخدمين أو يزوّد فاتورة السيرفر.

صفوف خوادم في مركز بيانات ليلي ترمز لبوابة API تتحكم في معدل الطلبات الواردة

ليه العدّاد البسيط بيفشل

أول حل بيخطر على البال: عدّاد ثابت لكل نافذة. "مسموح 60 طلب في الدقيقة"، فبتزوّد مفتاح في Redis وبتصفّره كل دقيقة. المشكلة إن ده بيسمح بضعف الحد على حدود النافذة. لو العميل بعت 60 طلب في الثانية 59، و60 تاني في الثانية 61، يبقى بعت 120 طلب في ثانيتين وانت فاكر إنك حاميت. ده اسمه مشكلة حدود النافذة (window boundary).

إزاي بتشتغل خوارزمية دلو الرموز

الفكرة العلمية بسيطة. كل عميل عنده "دلو" فيه رموز (tokens). الدلو بيتعبّى بمعدل ثابت r رمز في الثانية، وله سعة قصوى C. كل طلب بياخد رمز واحد. لو في رمز، الطلب يعدّي والرصيد ينقص. لو الدلو فاضي، الطلب يترفض فورًا بخطأ HTTP 429.

الحاجة الذكية إن الرموز بتتراكم لما العميل يهدأ، لحد سقف السعة. يعني المتوسط على المدى الطويل بيفضل r طلب/ثانية، لكن الخوارزمية بتسمح بموجة (burst) حجمها C. ده اللي بيخلّيها ألطف من العدّاد الجامد للمستخدم الحقيقي.

التطبيق العملي: Redis + Lua ذري

عشان تشتغل مع أكتر من سيرفر تطبيق في نفس الوقت، محتاج مكان مركزي للحالة، و Redis مثالي لأنه بيشغّل سكربت Lua بشكل ذري (atomic)، فمفيش طلبين بيقروا نفس الرصيد ويصرفوه مرتين. ده السكربت كامل قابل للنسخ:

-- KEYS[1] = مفتاح دلو العميل، مثال: rl:user:42
-- ARGV[1] = السعة C          ARGV[2] = معدل التعبئة r (رمز/ثانية)
-- ARGV[3] = الوقت الحالي now (ثواني)   ARGV[4] = الرموز المطلوبة (عادة 1)
local capacity = tonumber(ARGV[1])
local refill   = tonumber(ARGV[2])
local now      = tonumber(ARGV[3])
local needed   = tonumber(ARGV[4])

local d      = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local tokens = tonumber(d[1])
local ts     = tonumber(d[2])
if tokens == nil then          -- أول مرة: الدلو مليان
  tokens = capacity
  ts = now
end

-- ضيف الرموز المتراكمة منذ آخر طلب، بحد أقصى السعة
local delta = math.max(0, now - ts)
tokens = math.min(capacity, tokens + delta * refill)

local allowed = tokens >= needed
if allowed then tokens = tokens - needed end

redis.call('HMSET', KEYS[1], 'tokens', tokens, 'ts', now)
redis.call('EXPIRE', KEYS[1], math.ceil(capacity / refill) * 2)  -- تنظيف تلقائي للخاملين

return { allowed and 1 or 0, math.floor(tokens) }

بتناديه من التطبيق بـ EVAL وتمرّر مفتاح العميل والأرقام. لو رجّع 0 في أول قيمة، ابعت للعميل استجابة 429 Too Many Requests ومعاها هيدر Retry-After. لاحظ إن EXPIRE بيمسح مفاتيح العملاء الخاملين تلقائيًا فالذاكرة متفضلش تكبر.

بديل بدون كود: NGINX على الحافة

لو عايز حماية أبسط قبل ما الطلب يوصل تطبيقك أصلًا، NGINX بيعمل تحديد معدل على مستوى الحافة (بخوارزمية leaky bucket، قريبة من دلو الرموز):

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

server {
  location /api/ {
    limit_req zone=api burst=20 nodelay;   # 10 طلب/ث + موجة 20
    proxy_pass http://backend;
  }
}

الفرق: الـ leaky bucket بيصرّف الطلبات بمعدل ثابت وبيرفض الزيادة، بينما دلو الرموز بيسمح بالموجة تعدّي على طول لو في رصيد. للـ API العام، NGINX كافي وأرخص. لو محتاج حدود مختلفة لكل مستخدم أو خطة اشتراك، استخدم Redis.

الأرقام: قبل وبعد

خد سيناريو واقعي: API بيخدم 30 ألف مستخدم/يوم. بدون تحديد معدل، سكربت واحد فلت من عميل قدر يبعت حوالي 2000 طلب/ثانية، فقفز استهلاك الـ CPU على السيرفر لحوالي 85% وبقى زمن الاستجابة P95 عند 1.8 ثانية لباقي المستخدمين. بعد تفعيل دلو الرموز بحد 10 طلب/ثانية وسعة 20، الطلبات الزيادة بترجع 429 في أقل من 1 مللي ثانية، والـ P95 رجع تحت 180 مللي ثانية. تكلفة القرار: كل طلب بياخد round-trip زيادة لـ Redis حوالي 0.3 إلى 0.5 مللي ثانية (أرقام تقديرية بتعتمد على الشبكة وحجم الحمل).

المقايضات (trade-offs)

  • بتكسب حماية عادلة وثبات تحت الضغط، بتخسر استدعاء Redis لكل طلب (latency بسيطة + اعتماد على توفّر Redis).
  • السعة الكبيرة بتحسّن تجربة المستخدم الحقيقي، بس بتوسّع الباب للموجات المؤذية. السعة الصغيرة بتقفل الموجة بس بتزعّل عملاء شرعيين بيعملوا طلبات متوازية.
  • لو Redis وقع، لازم تقرر: تسمح للكل (fail-open) وتخاطر بالحمل، ولا ترفض الكل (fail-closed) وتخاطر بتعطيل الخدمة. الاختيار حسب حساسية الـ API.

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

لو عندك سيرفر واحد وترافيك بسيط، متعقّدش الأمور بـ Redis؛ عدّاد في الذاكرة أو limit_req في NGINX أكفى. ولو المشكلة إن عملية واحدة تقيلة (زي تصدير تقرير ضخم) بتخنق السيرفر، فدي مشكلة queue أو timeouts مش rate limiting. وكمان لو محتاج عدالة دقيقة جدًا في التوزيع بين آلاف المستأجرين، ابص على خوارزميات زيّ sliding window log اللي بتديك دقة أعلى مقابل ذاكرة أكبر.

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

افتح أبطأ endpoint في الـ API بتاعك، وضيف عليه دلو رموز بحد 10 طلب/ثانية وسعة 20 لكل مستخدم باستخدام السكربت فوق. بعدها اعمل حمل تجريبي بـ hey -z 30s -c 50 وشوف نسبة الـ 429 وزمن الـ P95 قبل وبعد. لو الأرقام اتحسنت زي المتوقع، عمّم الإعداد على باقي الـ endpoints الحساسة.

المصادر

  • توثيق Redis الرسمي لبناء rate limiter بخوارزمية دلو الرموز: redis.io/docs/latest/develop/use-cases/rate-limiter
  • توثيق NGINX لوحدة ngx_http_limit_req_module: nginx.org/en/docs/http/ngx_http_limit_req_module.html
  • مدونة Stripe عن تصميم أنظمة تحديد المعدل في الإنتاج: stripe.com/blog/rate-limiters
  • توثيق MDN لكود الحالة 429 Too Many Requests: developer.mozilla.org/en-US/docs/Web/HTTP/Status/429

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

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

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