معالجة خطأ 429 API وتخزين الاستجابات مؤقتًا

الخطأ 429 rate_limit_exceeded يعني الانتظار حسب الترويسة Retry-After ثم المحاولة مجددًا، أما 402 insufficient_credits فيعني أن رصيدك قد نفد، فأضف رصيدًا. ويُبقي التخزين المؤقت الصغير الطلبات المتكررة خارج فاتورتك. اضبط مدة انتهائه بحسب وتيرة تغير البيانات.

نفّذ ذلك باستخدام واجهة API

  1. جهّز مفتاحك احصل على مفتاح API مجاني واحفظه في متغير البيئة NARWHAL_API_KEY. يقرأ سكربت Python هذا المتغير ويرسل المفتاح بصيغة Authorization: Bearer YOUR_KEY إلى https://api.narwhalapi.com.
  2. اختر مدة انتهاء لكل نوع بيانات خزّن تقاويم العطل أيامًا أو أسابيع، والإصدارات الاقتصادية حتى الإصدار المجدول التالي، ومنحنيات العائد ليوم واحد. يمكن أن تبقى أسعار الصرف اليومية مخزّنة حتى يوم UTC التالي؛ استخدم هذه الحدود عند اختيار مدة الصلاحية أو TTL.
  3. استخدم دالة التخزين المؤقت وإعادة المحاولة يجمع مفتاح التخزين المؤقت بين المسار والمعاملات المرتبة، فيكون لكل طلب بمعاملات مختلفة إدخال منفصل. يقرأ السكربت سعر الذهب مرتين بالمسار نفسه والـ TTL نفسه، وتستخدم القراءة الثانية الاستجابة المخزّنة ما دام إدخالها ضمن مدة TTL.
    Python: تخزين مؤقت مع معالجة Retry-After
    import os, random, time
    import requests
    
    API = "https://api.narwhalapi.com"
    session = requests.Session()
    session.headers["Authorization"] = f"Bearer {os.environ['NARWHAL_API_KEY']}"
    _cache: dict[str, tuple[float, dict]] = {}
    
    
    def get(path: str, ttl: int = 300, **params):
        """GET with a small in-memory cache, Retry-After on 429s and
        backoff on 5xx."""
        key = path + repr(sorted(params.items()))
        hit = _cache.get(key)
        if hit and time.time() - hit[0] < ttl:
            return hit[1]
        for attempt in range(4):
            r = session.get(API + path, params=params, timeout=10)
            if r.status_code == 402:
                raise RuntimeError("Out of credit; add credit in the dashboard.")
            if r.status_code == 429:
                time.sleep(
                    float(r.headers.get("Retry-After", 2))
                )  # short-window limit: wait, then retry
                continue
            if r.status_code in (500, 503):
                time.sleep(
                    2**attempt + random.random()
                )  # bounded exponential backoff with jitter
                continue
            r.raise_for_status()
            _cache[key] = (time.time(), r.json())
            return _cache[key][1]
        raise RuntimeError(f"Gave up on {path}")
    
    
    print(get("/v1/commodities/spot/XAU/USD", ttl=60)["price"])
    print(get("/v1/commodities/spot/XAU/USD", ttl=60)["price"], "(from cache)")
  4. اقرأ ترويسات الاستجابة تتضمن استجابات البيانات الناجحة X-Request-ID، وفي كل دقيقة X-RateLimit-Limit وX-RateLimit-Remaining وX-RateLimit-Reset، إضافة إلى X-Narwhal-Cost وX-Narwhal-Balance الخاصتين برصيدك.

يمكن للحسابات ذات الرصيد المجاني إرسال 50 طلبًا في الدقيقة، وللحسابات ذات الرصيد المدفوع 500.

لا تخزّن الدالة الاستجابة إلا بعد طلب ناجح. يُعاد الإدخال غير المنتهي قبل إجراء استدعاء HTTP، أما الإدخال المنتهي فيصل إلى حلقة الطلبات مجددًا. وذاكرتها المؤقتة في الذاكرة العاملة، فاستخدم خيار الذاكرة المشتركة عندما تحتاج خدمة الويب لديك إلى ذلك.

قيمة TTL الافتراضية هي 300 ثانية، وتتجاوزها استدعاءات سعر الذهب بالقيمة ttl=60. لكل طلب مهلة قدرها 10 ثوانٍ، وتسمح الحلقة بما يصل إلى 4 محاولات. في حالة حد النافذة القصيرة تقرأ الدالة Retry-After على أنه تأخير بالثواني وتنتظر قبل المحاولة التالية، وإذا غابت هذه الترويسة تستخدم 2 ثانية. وعند استنفاد المحاولات ترفع الدالة خطأً يتضمن المسار المطلوب. أما حالات الفشل الأخرى فتصل إلى raise_for_status() دون إضافة استجابة إلى الذاكرة المؤقتة.

معالجة إعادة المحاولة بدون كود

في Zapier أو Make أو n8n أضف خطوة تأخير قصيرة أو فعّل إعادة المحاولة عند الخطأ في المنصة. تعرض هذه المنصات حالة HTTP أصلًا. عند الخطأ 429 اقرأ code في متن المشكلة قبل أن تقرر هل تؤخر وتعيد المحاولة أم تتوقف.

جدول الطلبات بفاصل مناسب للبيانات التي تستخدمها. ينطبق توقيت التخزين المؤقت نفسه على الأتمتة: تستطيع تقاويم العطل أن تنتظر أيامًا أو أسابيع بين القراءات، ويمكن إبقاء الإصدار الاقتصادي مخزّنًا حتى موعده المجدول التالي. باعد بين الطلبات عند حدوث دفعة منها لتبقى تحت حد الدقيقة.

متى تستخدم كل خيار

الحالةالخيار الأنسبالسبب
سكربت يعمل مرة في اليوملا حاجة إلى تخزين مؤقتالسكربت يباعد قراءاته يومًا بالفعل
خدمة ويب تكرر الطلباتذاكرة مؤقتة في الذاكرة العاملة أو مشتركةيُبقي ضبط TTL لكل نوع بيانات القراءات المتكررة خارج فاتورتك
وصول طلبات كثيرة دفعة واحدةباعد بين الطلباتيظل حد الدقيقة سارٍ

يحدد TTL المدة التي يمكن فيها إعادة استخدام الاستجابة، أما المباعدة بين الطلبات فتتحكم في وقت وصول الاستدعاءات إلى الـ API. وقد تحتاج خدمة الويب إلى الاثنين: يعالج التخزين المؤقت القراءات المتكررة لنفس المسار والمعاملات، وتعالج المباعدة الاستدعاءات التي تصل إلى الـ API رغم ذلك. ويبقى التخزين لمدة ساعة تاركًا الطلبات ذات المسارات أو المعاملات المختلفة استدعاءات منفصلة. احسب هذه الاستدعاءات عند تقرير ما إذا كانت دفعة الطلبات تحتاج إلى مباعدة، حتى لو استخدمت قراءات أخرى استجابات مخزّنة.

هل يُحتسب الخطأ 429 أو 402 ضمن الرسوم؟

لا، لا تُحتسب الأخطاء أبدًا، سواء كان 429 لحد المعدل أو 402 لنفاد الرصيد. تستخدم متون الأخطاء application/problem+json وتتضمن status وcode وdetail وrequest_id. ويحدد code سبب الاستجابة.

عند نفاد رصيدك أضف رصيدًا من لوحة التحكم. ترفع الدالة خطأً عند 402 بدل متابعة حلقة إعادة المحاولة، لأن التأخير القصير لا يضيف رصيدًا.

هل يمكن تقييد العملاء الذين يشتركون في عنوان واحد معًا؟

نعم، قد يخضع العملاء الذين يشتركون في عنوان واحد للتقييد معًا: تسمح حاوية منفصلة لمكافحة إساءة الاستخدام بـ 120 طلبًا في الدقيقة مع دفعة قدرها 20 لهوية شبكة واحدة. يهمّ كل من حدود الحساب وهذه الحاوية المشتركة عند وصول الطلبات دفعة واحدة.

ما الأخطاء الأخرى التي ينبغي إعادة المحاولة بعدها؟

أعد محاولة الطلبات متساوية الأثر بعد 500 أو 503 باستخدام تراجع أسي محدود مع تشويش عشوائي. تزيد الدالة التأخير بين المحاولات وتضيف عنصرًا عشوائيًا، وبعد انتهاء حلقة الطلبات المحدودة ترفع خطأً إذا لم تنجح أي استجابة.

لا تعد محاولة 400، ولا تعد محاولة 401 بالمفتاح نفسه. قد يصبح 404 series_not_found متاحًا بعد صدور إحدى النشرات، فافصل هذه الحالة عن أخطاء الخادم التي تعالجها حلقة التراجع.

مثال حسابي: تكلفة لوحة معلومات

بسعر $2 لكل 1,000 استدعاء، يغطي رصيد مجاني قدره $10 ما يعادل 5,000 استدعاء شهريًا، أي نحو 166 يوميًا. ابدأ بلوحة معلومات تقرأ 6 قيم كل 15 دقيقة. يعني جدول التحديث هذا 96 قراءة لكل قيمة يوميًا، فاضرب 6 في 96 لتحصل على 576 طلبًا يوميًا.

ثم خزّن كل قيمة مؤقتًا لمدة ساعة. تعيد تحديثات لوحة المعلومات خلال تلك الساعة استخدام الاستجابة المخزّنة، فينخفض عدد طلبات الـ API إلى 144 يوميًا. ويمكنك أيضًا إبطاء التحديث عندما تكلّف اللوحة أكثر مما تريد.

الحسابالطلبات اليومية
رصيد مجاني قدره $10 بسعر $2 لكل 1,000 استدعاء، معبَّرًا عنه كمتوسط يومينحو 166
6 قيم × 96 قراءة مع تحديث كل 15 دقيقة576
القيم الـ 6 نفسها، تُخزَّن كل منها ساعة144

نُشر في .

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