ملخص
قم بتطبيق تحديد معدل استدعاءات API باستخدام خوارزميات سلة الرموز (token bucket) أو النافذة المنزلقة (sliding window). أرجع رؤوس (headers) تحديد المعدل القياسية لـ IETF (RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset) و 429 Too Many Requests عند تجاوز الحدود. Modern PetstoreAPI يطبق تحديد المعدل مع حصص مخصصة لكل مستخدم واستجابات خطأ واضحة.
مقدمة
يقوم عميل واحد بإجراء 10,000 طلب إلى واجهة برمجة التطبيقات (API) الخاصة بك في دقيقة واحدة. يتعطل خادم قواعد البيانات الخاص بك. تنطلق تنبيهات المراقبة الخاصة بك. لا يتمكن عملاؤك الآخرون من الوصول إلى API. أنت تحت هجوم—أو ربما تتعامل فقط مع عميل به خطأ في حلقة إعادة المحاولة.
يمنع تحديد المعدل هذا. فهو يحدد عدد الطلبات التي يمكن للعميل إجراؤها في نافذة زمنية معينة. عندما يتجاوزون الحد، ترجع استجابة 429 Too Many Requests. يتراجع العميل، وتبقى واجهة برمجة التطبيقات الخاصة بك تعمل بشكل صحي.
لا يطبق Swagger Petstore القديم تحديد المعدل على الإطلاق. Modern PetstoreAPI يطبق تحديد المعدل باستخدام رؤوس IETF القياسية، وحصص مخصصة لكل مستخدم، واستجابات خطأ واضحة.
في هذا الدليل، ستتعلم خوارزميات تحديد المعدل، والرؤوس القياسية، وكيف يطبق Modern PetstoreAPI تحديد المعدل بشكل صحيح.
لماذا تحتاج واجهات برمجة التطبيقات إلى تحديد المعدل
يحمي تحديد المعدل واجهة برمجة التطبيقات (API) الخاصة بك من الإساءة ويضمن الاستخدام العادل.
الحماية ضد الإساءة
1. هجمات حجب الخدمة (DoS)
يغمر المهاجم واجهة برمجة التطبيقات الخاصة بك بالطلبات لجعلها غير متاحة. يحد تحديد المعدل من تأثيرهم.
2. حشو بيانات الاعتماد
يجرب المهاجمون الآلاف من تركيبات اسم المستخدم/كلمة المرور. يبطئ تحديد المعدل من وتيرتهم.
3. كشط البيانات
تقوم الروبوتات بكشط مجموعة البيانات بأكملها. يجعل تحديد المعدل عملية الكشط غير عملية.
4. التحكم في التكلفة
إذا كانت واجهة برمجة التطبيقات الخاصة بك تستدعي خدمات باهظة الثمن (نماذج الذكاء الاصطناعي، واجهات برمجة تطبيقات الطرف الثالث)، يمنع تحديد المعدل التكاليف الجامحة.
الاستخدام العادل
1. منع عميل واحد من احتكار الموارد
بدون تحديد المعدل، يمكن لعميل واحد يقوم بإجراء 1000 طلب/ثانية أن يحرم العملاء الآخرين.
2. أداء يمكن التنبؤ به
يضمن تحديد المعدل أوقات استجابة متسقة لجميع العملاء.
3. الوصول المتدرج
الطبقة المجانية: 100 طلب/ساعة. الطبقة المدفوعة: 10,000 طلب/ساعة. يفرض تحديد المعدل هذه الطبقات.
الفوائد التشغيلية
1. تخطيط السعة
تعرف أقصى حمل يمكن لواجهة برمجة التطبيقات الخاصة بك التعامل معه.
2. القدرة على التنبؤ بالتكلفة
تحدد قيود المعدل تكاليف البنية التحتية.
3. التدهور اللطيف
تحت الحمل، يمنع تحديد المعدل الانهيارات المتتالية.
خوارزميات تحديد المعدل
تتميز الخوارزميات المختلفة بمقايضات مختلفة.
1. النافذة الثابتة
عد الطلبات في نوافذ زمنية ثابتة.
كيف يعمل:
النافذة 1 (00:00-00:59): مسموح بـ 100 طلب
النافذة 2 (01:00-01:59): مسموح بـ 100 طلب
التنفيذ:
def is_allowed(user_id):
current_minute = get_current_minute()
key = f"rate_limit:{user_id}:{current_minute}"
count = redis.incr(key)
redis.expire(key, 60)
return count <= 100
المزايا:
- سهل التنفيذ
- استخدام منخفض للذاكرة
العيوب:
- مشكلة الاندفاع: يمكن للعميل إجراء 100 طلب في 00:59 و 100 طلب في 01:00 (200 طلب في ثانيتين)
2. النافذة المنزلقة
عد الطلبات في نافذة زمنية متجددة.
كيف يعمل:
في 01:30، عد الطلبات من 00:30 إلى 01:30 (آخر 60 دقيقة).
التنفيذ:
def is_allowed(user_id):
now = time.time()
window_start = now - 3600 # قبل ساعة واحدة
key = f"rate_limit:{user_id}"
# إزالة الطلبات القديمة
redis.zremrangebyscore(key, 0, window_start)
# عد الطلبات في النافذة
count = redis.zcard(key)
if count < 100:
redis.zadd(key, {now: now})
redis.expire(key, 3600)
return True
return False
المزايا:
- لا توجد مشكلة اندفاع
- تحديد معدل دقيق
العيوب:
- استخدام أعلى للذاكرة (يخزن الطابع الزمني لكل طلب)
- أكثر تعقيدًا
3. سلة الرموز (Token Bucket)
تُضاف الرموز إلى سلة بمعدل ثابت. يستهلك كل طلب رمزًا واحدًا.
كيف يعمل:
سعة السلة: 100 رمز
معدل التعبئة: 10 رموز/ثانية
الطلب: يستهلك رمزًا واحدًا
التنفيذ:
def is_allowed(user_id):
now = time.time()
key = f"rate_limit:{user_id}"
# الحصول على الحالة الحالية
data = redis.hgetall(key)
tokens = float(data.get('tokens', 100))
last_refill = float(data.get('last_refill', now))
# إعادة تعبئة الرموز
elapsed = now - last_refill
tokens = min(100, tokens + elapsed * 10) # 10 رموز/ثانية
if tokens >= 1:
tokens -= 1
redis.hset(key, 'tokens', tokens)
redis.hset(key, 'last_refill', now)
redis.expire(key, 3600)
return True
return False
المزايا:
- يسمح بالاندفاعات (حتى سعة السلة)
- تحديد معدل سلس
- معيار صناعي
العيوب:
- أكثر تعقيدًا من النافذة الثابتة
- يتطلب تخزين الحالة
4. السلة المتسربة (Leaky Bucket)
تُضاف الطلبات إلى قائمة انتظار وتُعالج بمعدل ثابت.
كيف يعمل:
سعة قائمة الانتظار: 100 طلب
معدل المعالجة: 10 طلبات/ثانية
المزايا:
- معدل إخراج سلس
- جيد لحماية الخدمات النهائية
العيوب:
- يضيف زمن استجابة (تنتظر الطلبات في قائمة الانتظار)
- معقدة التنفيذ
ما هي الخوارزمية التي يجب استخدامها؟
لمعظم واجهات برمجة التطبيقات: سلة الرموز (Token Bucket)
إنه المعيار الصناعي، ويسمح بحدوث اندفاعات معقولة، ويوفر تحديد معدل سلس.
Modern PetstoreAPI يستخدم سلة الرموز مع حصص مخصصة لكل مستخدم.
رؤوس تحديد المعدل القياسية
استخدم رؤوس IETF القياسية (draft-ietf-httpapi-ratelimit-headers).
الرؤوس القياسية
RateLimit-Limit: الحد الأقصى للطلبات المسموح بها في النافذة الزمنية
RateLimit-Limit: 100
RateLimit-Remaining: الطلبات المتبقية في النافذة الحالية
RateLimit-Remaining: 45
RateLimit-Reset: الثواني المتبقية حتى تتم إعادة تعيين حد المعدل
RateLimit-Reset: 3600
مثال على الاستجابة
GET /pets
200 OK
RateLimit-Limit: 100
RateLimit-Remaining: 99
RateLimit-Reset: 3600
{
"data": [...]
}
الرؤوس القديمة (مهملة)
تستخدم العديد من واجهات برمجة التطبيقات رؤوسًا غير قياسية:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1710331200
لا تستخدم هذه. البادئة X- مهملة، والتنسيق غير موحد.
كيف يطبق Modern PetstoreAPI تحديد المعدل
Modern PetstoreAPI يطبق تحديد معدل سلة الرموز (token bucket) باستخدام رؤوس قياسية.
حدود المعدل حسب الطبقة
الطبقة المجانية:
- 100 طلب/ساعة
- 1,000 طلب/يوم
الطبقة الاحترافية:
- 10,000 طلب/ساعة
- 100,000 طلب/يوم
طبقة المؤسسات:
- حدود مخصصة
التنفيذ
طلب ناجح:
GET /v1/pets
200 OK
RateLimit-Limit: 100
RateLimit-Remaining: 99
RateLimit-Reset: 3540
{
"data": [...]
}
تجاوز حد المعدل:
GET /v1/pets
429 Too Many Requests
Content-Type: application/problem+json
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 120
Retry-After: 120
{
"type": "https://petstoreapi.com/errors/rate-limit-exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "You have exceeded the rate limit of 100 requests per hour",
"instance": "/v1/pets",
"retryAfter": 120,
"limit": 100,
"window": "1h"
}
لكل مستخدم مقابل لكل عنوان IP
لكل مستخدم (طلبات مصادق عليها):
تحديد المعدل حسب معرف المستخدم أو مفتاح API. أكثر دقة وعدلاً.
user_id = get_authenticated_user()
is_allowed(user_id)
لكل عنوان IP (طلبات غير مصادق عليها):
تحديد المعدل حسب عنوان IP. أقل دقة (عناوين IP مشتركة، شبكات VPN) ولكنه أفضل من لا شيء.
ip_address = request.remote_addr
is_allowed(ip_address)
يستخدم Modern PetstoreAPI تحديد المعدل لكل مستخدم للطلبات المصادق عليها، ولكل عنوان IP لنقاط النهاية العامة.
تنسيق استجابة تحديد المعدل
عند تجاوز حدود المعدل، أرجع 429 بتنسيق خطأ RFC 9457.
هيكل الاستجابة
{
"type": "https://petstoreapi.com/errors/rate-limit-exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "You have exceeded your rate limit. Please try again later.",
"instance": "/v1/pets",
"retryAfter": 120,
"limit": 100,
"remaining": 0,
"reset": 120,
"window": "1h"
}
الرؤوس
429 Too Many Requests
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 120
Retry-After: 120
Retry-After: يخبر العملاء متى يمكنهم إعادة المحاولة (بالثواني).
اختبار حدود المعدل باستخدام Apidog
Apidog يساعدك على اختبار سلوك تحديد المعدل.
سيناريوهات الاختبار
1. الاستخدام العادي:
إرسال 50 طلبًا ← تنجح جميعها
تحقق من انخفاض RateLimit-Remaining
2. تجاوز الحد:
إرسال 101 طلبًا ← يعيد الطلب الـ 101 استجابة 429
تحقق من تنسيق استجابة الخطأ
تحقق من رأس Retry-After
3. سلوك إعادة التعيين:
تجاوز الحد ← انتظر حتى إعادة التعيين ← تحقق من استعادة الحد
4. طبقات مختلفة:
اختبار الطبقة المجانية (100/ساعة)
اختبار الطبقة الاحترافية (10,000/ساعة)
تحقق من تطبيق الحدود بشكل صحيح
مثال على اختبار Apidog
// اختبار رؤوس تحديد المعدل
pm.test("Rate limit headers present", () => {
pm.response.to.have.header("RateLimit-Limit");
pm.response.to.have.header("RateLimit-Remaining");
pm.response.to.have.header("RateLimit-Reset");
});
// اختبار تجاوز حد المعدل
pm.test("Returns 429 when limit exceeded", () => {
// إجراء 101 طلب
for (let i = 0; i < 101; i++) {
pm.sendRequest("GET /v1/pets");
}
pm.response.to.have.status(429);
});
أفضل الممارسات لتحديد المعدل
1. استخدم رؤوسًا قياسية
استخدم رؤوس IETF القياسية، وليس رؤوس X- المخصصة.
2. أرجع 429، وليس 403
429 تعني "طلبات كثيرة جدًا". 403 تعني "ممنوع". لا تخلط بينهما.
3. قم بتضمين Retry-After
أخبر العملاء متى يمكنهم إعادة المحاولة.
4. وثّق حدودك
اجعل حدود المعدل مرئية في الوثائق.
5. قدم طبقات مختلفة
الطبقة المجانية: حدود منخفضة. الطبقة المدفوعة: حدود أعلى.
6. حدد المعدل حسب المستخدم، وليس عنوان IP
الحدود لكل مستخدم أكثر دقة وعدلاً.
7. اسمح بالاندفاعات
تسمح سلة الرموز بحدوث اندفاعات معقولة دون معاقبة الاستخدام العادي.
8. راقب تجاوزات حد المعدل
تتبع عدد المرات التي يتجاوز فيها العملاء حدود المعدل. المعدلات العالية تشير إلى مشاكل.
9. توفير نقطة نهاية لحالة تحديد المعدل
GET /v1/rate-limit
200 OK
{
"limit": 100,
"remaining": 45,
"reset": 3540
}
10. اختبر تحديد المعدل
استخدم Apidog لاختبار سلوك تحديد المعدل قبل النشر.
الخلاصة
يحمي تحديد المعدل واجهة برمجة التطبيقات الخاصة بك من الإساءة ويضمن الاستخدام العادل. استخدم خوارزمية سلة الرموز مع رؤوس IETF القياسية (RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset). أرجع 429 Too Many Requests بتنسيق خطأ RFC 9457 عند تجاوز الحدود.
Modern PetstoreAPI يطبق تحديد المعدل بشكل صحيح مع حصص لكل مستخدم، ورؤوس قياسية، واستجابات خطأ واضحة. تحقق من الوثائق للحصول على تفاصيل التنفيذ.
اختبر تحديد المعدل الخاص بك باستخدام Apidog للتأكد من أنه يعمل بشكل صحيح تحت الحمل ويتعامل مع الحالات الهامشية بشكل مناسب.
الأسئلة الشائعة
ما هي حدود المعدل التي يجب أن أضعها؟
ابدأ بتحفظ: 100 طلب/ساعة للطبقة المجانية، و 10,000 طلب/ساعة للطبقة المدفوعة. اضبط بناءً على أنماط الاستخدام وسعة البنية التحتية.
هل يجب أن أحدد المعدل حسب عنوان IP أم المستخدم؟
حدد المعدل حسب المستخدم (مفتاح API) للطلبات المصادق عليها. استخدم تحديد المعدل المستند إلى عنوان IP فقط لنقاط النهاية العامة.
ماذا يحدث إذا تجاوز العميل حد المعدل؟
أرجع 429 Too Many Requests مع رأس Retry-After. لا تحظر العميل بشكل دائم—اسمح لهم بإعادة المحاولة بعد إعادة تعيين النافذة.
كيف أتعامل مع حدود المعدل لخطافات الويب (webhooks)؟
خطافات الويب هي من خادم إلى خادم، لذا يجب أن تكون حدود المعدل أعلى. ضع في اعتبارك حدودًا منفصلة لخطافات الويب مقابل استدعاءات API.
هل يجب أن أحدد المعدل للخدمات الداخلية؟
نعم، ولكن بحدود أعلى بكثير. يمنع تحديد المعدل الانهيارات المتتالية حتى في الأنظمة الداخلية.
كيف أختبر تحديد المعدل؟
استخدم Apidog لإرسال طلبات متعددة والتحقق من استجابات 429، ورؤوس تحديد المعدل، وسلوك إعادة التعيين.
ماذا لو كانت واجهة برمجة التطبيقات الخاصة بي خلف شبكة CDN؟
يقلل التخزين المؤقت لـ CDN الحمل، ولكنك لا تزال بحاجة إلى تحديد المعدل لعمليات عدم توفر التخزين المؤقت (cache misses) وطلبات POST/PUT/DELETE.
كيف أطبق تحديد المعدل عبر خوادم متعددة؟
استخدم مخزن بيانات مشتركًا (Redis, Memcached) لتتبع حدود المعدل عبر جميع الخوادم. لا تستخدم الذاكرة المحلية—لن يعمل ذلك في الأنظمة الموزعة.
