تقييد الاتصال بالواجهة — الرسائل، التراجع التبادلي، والبقاء في حالة 429

تحديث في

لقد واجهت 429. الواجهة تخبرك أن تبطئ. إليك كيف تحلل رموز X-RateLimit-*، تفهم Retry-After، وتطبيق التراجع التبادلي مع التقلبات بحيث يتعامل تكاملاتك مع الحدود بسلاسة بدلًا من تكرار الضغط على الخادم.

الحد المسموح به في واجهة الواجهة — العناصر، إعادة المحاولة التبادلية، والبقاء في 429 1
إعلان · حذف؟

لقد واجهت خطأ 429. ربما انهار مُعالج الويب. أو ربما أُسَلِّمَت مهام مُجمعة بشكل سري. أعطى الواجهة "عدد الطلبات الزائد" مع سلسلة من عناصر الرد التي ربما تخطيت عددها.

هذه العناصر هي القصة الكاملة. إليك كيفية قراءتها، وكيفية كتابة منطق إعادة المحاولة الذي لا يُفاقم المشكلة.

العناصر التي تهمك

تُرجع معظم واجهات التحكم المُقيّدة بعض الأشكال من هذه العناصر في كل رد – ليس فقط عند 429:

  • X-RateLimit-Limit — عدد الطلبات المسموح بها في النافذة الحالية. تُعطي واجهة REST الخاصة بـ GitHub للمستخدمين المُعتمدين 5000 في الساعة؛ بينما الطلبات غير المُعتمدة تصل إلى 60.
  • X-RateLimit-Remaining — عدد الطلبات المتبقية في النافذة الحالية. عندما تصل إلى 0، فإن الطلب التالي يُرجع خطأ 429.
  • X-RateLimit-Reset — وقت إعادة تعيين النافذة، كمُدخل زمني لـ Unix. هذا هو العنصر الذي يُتجاهله معظم المطورين، وهو الأفضل استخدامه.
  • X-RateLimit-Used (مخصص لـ GitHub) — عدد الطلبات المستهلكة حتى الآن. يُماثل Limit - Remaining ولكنه مفيد للتحقق من السياق.
  • Retry-After — يظهر فقط في ردود 429. إما عدد ثوانٍ لانتظار، أو سلسلة تاريخية HTTP. إذا أرسلها الواجهة، استخدمها — فهي أكثر دقة من أي حساب تقوم به أنت.

العناصر المُستخدمة في رد GitHub الحقيقي تبدو كالتالي:

X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4823
X-RateLimit-Reset: 1716998400
X-RateLimit-Used: 177
X-RateLimit-Resource: core

ال X-RateLimit-Resource العنصر هو مخصص لـ GitHub: يُحافظ على مجموعات من السعة الخاصة بـ REST، والبحث، وGraphQL. استهلاك السعة الخاصة بالبحث (محدود بـ 30 طلب في الدقيقة) لا يؤثر على السعة الخاصة بالأساس — والعكس صحيح.

Stripe يختلف

لا يستخدم X-RateLimit-* مُسمى التسمية. عناصرها مُقَسَّمة بشكل مختلف:

Stripe-Ratelimit-Limit: 100
Stripe-Ratelimit-Remaining: 97
Stripe-Ratelimit-Reset: 1716998460

وإذا كانت 429:

Retry-After: 30

يُعد الحد الافتراضي لـ Stripe هو 100 طلب نشط في وضع التشغيل، كل ثانيةوهو ليس على أساس الساعة. هذا مهم أكثر مما يبدو: يمكن أن يُستهلك هذا النطاق في أقل من 5 ثوانٍ إذا لم تُحدّث الطلب من جانبك.

يُميز أيضًا Stripe بين حدود السرعة للطلبات وحدود الموارد الخاصة (مثلاً، إنشاء عدد كبير من العملاء في فترة قصيرة). يحدد جسم رد 429 أي حد تم تجاوزه — يجب دائمًا تسجيل الجسم الكامل للرد، وليس فقط رمز الحالة.

فك تشفير وقت إعادة التعيين

ال X-RateLimit-Reset القيمة هي وقت Unix. 1716998400 لا يُظهر شيئًا واضحًا عند النظر إليه، لكنه سهل التحويل: استخدم محول الطابع الزمني يونكس لتحويله إلى وقت قابل للقراءة في الوقت العالمي (UTC) ورؤية مدى انتظار إعادة التعيين.

في الكود: reset_time - time.now() يُعطي عدد الثواني حتى إعادة التعيين. لكن تحقق من X-RateLimit-Remaining أولاً — إذا كنت تمتلك السعة، فلا حاجة لانتظار شيء.

ما يُخبرك به جسم 429

الرمز 429 وحده ليس كافيًا. يُحدد جسم الرد عادةً أي حد تم تجاوزه:

GitHub:

{
  "message": "API rate limit exceeded for user ID 12345.",
  "documentation_url": "https://docs.github.com/rest/overview/rate-limits"
}

Stripe:

{
  "error": {
    "code": "rate_limit",
    "message": "Too many requests hit the API too quickly.",
    "type": "invalid_request_error"
  }
}

OpenAI يذهب أبعد: يحدد الرسالة الخطأ ما إذا كان تجاوز حد السعة لكل دقيقة أو لكل دقيقة للطلبات، مما يغيّر استراتيجية إعادة المحاولة تمامًا. يجب دائمًا تسجيل جسم 429 الكامل.

إعادة المحاولة مع تقلبات

الحل البسيط: احتجز 429، انتظر 1 ثانية، ثم أعد المحاولة. هذا يفشل لسببين:

  • إذا كان لديك عاملين متعددين يُحاولون نفس النقطة، فسوف ينتظر كل واحد 1 ثانية ويحاول مرة أخرى معًا — هذا يُخلق موجة من المحاولات المتماسكة التي تعيد المشكلة.
  • 1 ثانية غير مفيدة إذا استهلكت السعة في كل ساعة أو كل يوم. ستحصل فقط على 3600 طلب إضافي 429.

الحل الصحيح هو إعادة المحاولة مع تقلبات تُزداد تدريجيًا: كل محاولة تنتظر أكثر من المرة السابقة، مع مكون عشوائي لتوزيع العملاء المتوازيين.

import time
import random
import requests

def fetch_with_backoff(url, headers, max_retries=5):
    base_delay = 1  # seconds

    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)

        if response.status_code != 429:
            return response

        # Prefer Retry-After if the API provides it
        retry_after = response.headers.get("Retry-After")
        if retry_after:
            wait = int(retry_after)
        else:
            # Fall back to X-RateLimit-Reset
            reset = response.headers.get("X-RateLimit-Reset")
            if reset:
                wait = max(0, int(reset) - int(time.time()))
            else:
                # Pure exponential backoff with full jitter
                cap = 60  # max wait: 60s
                wait = random.uniform(0, min(cap, base_delay * (2 ** attempt)))

        print(f"Rate limited. Attempt {attempt + 1}/{max_retries}. Waiting {wait:.1f}s")
        time.sleep(wait)

    raise Exception(f"Max retries exceeded after {max_retries} attempts")

الترتيب المُحدد في هذا التنفيذ هو:

  • المحاولة الأولى باستخدام Retry-After — إذا أخبرك الواجهة بالوقت الدقيق للانتظار، استخدمه. لا تُحسبه بذكاء.
  • X-RateLimit-Reset كمُستند بديل — حسب عدد الثواني حتى إعادة التعيين بدلًا من تخمين تأخير ثابت.
  • التقلبات الكاملة كمُستند أخيرrandom.uniform(0, cap) تُوزع المحاولات عبر كامل فترة التأخير. يُوثق هذا في مدونة بنية AWS باسم "التقلبات الكاملة" ويُظهر أن هذا يقلل بشكل قابل للقياس من التصادم على الجانب الخادم مقارنة بالتقلبات المتساوية أو عدم وجود تقلبات.
  • max(0, ...) عند إعادة التعيين — يمكن أن يكون وقت إعادة التعيين في الماضي عند إجراء الحساب. احذر من أن يُنتج قيمة نهائية سلبية تُسبب توقف المُعالج.

الأخطاء الشائعة

تُعامل الأخطاء غير 429 كأخطاء للسرعة. الخطأ 503 هو خطأ خادم. الخطأ 401 يعني أن بياناتك خاطئة. تحقق من status_code == 429 بشكل مُحدد قبل تطبيق منطق إعادة المحاولة للسرعة.

إغلاق الخطأ 429 وعودة بيانات فارغة. الإخفاء يُصعب التحقيق مقارنة بالاستثناءات المُرفع. اعرض الخطأ.

استخدام تأخير ثابت. إذا استهلكت النافذة في كل ساعة مع 47 دقيقة متبقية على الساعة، فإن الانتظار لمدة 5 ثوانٍ لا يُفيدك. حسب وقت إعادة التعيين.

إعادة المحاولة بشكل لا نهائي. ضع max_retries حدًا وارتفع بعد أن تم استهلاكه. بعض الأخطاء 429 تشير إلى استهلاك السعة التي لا تُستعاد حتى فترة السداد التالية — تكرار غير محدود هو عيب.

عدم مراقبة X-RateLimit-Remaining بشكل مبكر. إذا Remaining تقل إلى أقل من 10% من Limit، ابدأ في توزيع الطلبات قبل أن تصل إلى الصفر. لا تفعل ذلك تلقائيًا في معظم مكتبات SDK. التكلفة هي بضع مللي ثانية من التأخير الإضافي؛ الفائدة هي تجنب رؤية خطأ 429 في البداية.

أحيانًا تكون أبسط الأدوات هي الأكثر ديمومة.

الخطأ 429 ليس مشكلة مُفردة تُعالج وتُنسى. إنه قيد متكرر، وتجاهل العناصر المرافقة يعني أنك ستجد نفس الحائط. استخدم Retry-After عندما تُقدّمها الواجهة. حسب X-RateLimit-Reset عندما لا تُقدّم. أضف تقلبات لتفادي توازي المحاولات. ضع حدًا لمنع تكرار غير محدود يصبح حادثًا في البيئة الإنتاجية.

وإذا كنت تنظر إلى X-RateLimit-Reset: 1716998400 وتسأل عن الوقت الذي يُعاد فيه — فإن محول الطابع الزمني يونكس سيخبرك في نقرة واحدة.

هل تريد حذف الإعلانات؟ تخلص من الإعلانات اليوم

تثبيت ملحقاتنا

أضف أدوات IO إلى متصفحك المفضل للوصول الفوري والبحث بشكل أسرع

أضف لـ إضافة كروم أضف لـ امتداد الحافة أضف لـ إضافة فايرفوكس أضف لـ ملحق الأوبرا

وصلت لوحة النتائج!

لوحة النتائج هي طريقة ممتعة لتتبع ألعابك، يتم تخزين جميع البيانات في متصفحك. المزيد من الميزات قريبا!

إعلان · حذف؟
إعلان · حذف؟
إعلان · حذف؟

ركن الأخبار مع أبرز التقنيات

شارك

ساعدنا على الاستمرار في تقديم أدوات مجانية قيمة

اشتري لي قهوة
إعلان · حذف؟