"إذا أراد العامل أن يؤدي عمله بشكل جيد، فعليه أولاً أن يشحذ أدواته." - كونفوشيوس، "مختارات كونفوشيوس. لو لينجونج"
الصفحة الأمامية > برمجة > شرح الاختناق: دليل لإدارة حدود طلبات واجهة برمجة التطبيقات (API).

شرح الاختناق: دليل لإدارة حدود طلبات واجهة برمجة التطبيقات (API).

تم النشر بتاريخ 2024-12-22
تصفح:381

متى يجب عليك تنفيذ التقييد في التعليمات البرمجية الخاصة بك؟

بالنسبة للمشاريع الكبيرة، من الأفضل عادةً استخدام أدوات مثل Cloudflare Rate Limiting أو HAProxy. إنها قوية وموثوقة وتتولى رفع الأحمال الثقيلة نيابةً عنك.

ولكن بالنسبة للمشاريع الصغيرة - أو إذا كنت تريد معرفة كيفية عمل الأشياء - يمكنك إنشاء محدد المعدل الخاص بك مباشرة في التعليمات البرمجية الخاصة بك. لماذا؟

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

ما سوف تتعلمه

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

  • حدود زمنية قابلة للتكوين: كل محاولة محظورة تزيد من مدة التأمين لمنع إساءة الاستخدام.
  • أحرف الطلب الكبيرة: قم بتعيين الحد الأقصى لعدد الطلبات المسموح بها. وهذا مفيد بشكل خاص لواجهات برمجة التطبيقات التي تتضمن خدمات مدفوعة، مثل OpenAI.
  • التخزين داخل الذاكرة: حل بسيط يعمل بدون أدوات خارجية مثل Redis - مثالي للمشاريع الصغيرة أو النماذج الأولية.
  • حدود لكل مستخدم: تتبع الطلبات على أساس كل مستخدم باستخدام عنوان IPv4 الخاص بهم. سنستفيد من SvelteKit لاسترداد عنوان IP للعميل بسهولة باستخدام طريقته المضمنة.

تم تصميم هذا الدليل ليكون نقطة بداية عملية، ومثاليًا للمطورين الذين يرغبون في تعلم الأساسيات دون تعقيدات غير ضرورية. لكنها ليست جاهزة للإنتاج.

قبل البدء، أريد منح الاعتمادات الصحيحة لقسم تحديد الأسعار في لوسيا.


تنفيذ خنق

دعونا نحدد فئة Throttler:

export class Throttler {
    private storage = new Map();

    constructor(private timeoutSeconds: number[]) {}
}

يقبل منشئ Throttler قائمة فترات المهلة (المهلة بالثواني). في كل مرة يتم فيها حظر مستخدم، تزداد المدة تدريجيًا بناءً على هذه القائمة. في النهاية، عند الوصول إلى المهلة النهائية، يمكنك أيضًا تشغيل رد اتصال لحظر عنوان IP الخاص بالمستخدم بشكل دائم - على الرغم من أن هذا خارج نطاق هذا الدليل.

إليك مثال لإنشاء مثيل مقيد يمنع المستخدمين من زيادة الفواصل الزمنية:

const throttler = new Throttler([1, 2, 4, 8, 16]);

سيقوم هذا المثيل بحظر المستخدمين في المرة الأولى لمدة ثانية واحدة. والمرة الثانية لشخصين، وهكذا.

نستخدم خريطة لتخزين عناوين IP والبيانات المقابلة لها. تعتبر الخريطة مثالية لأنها تتعامل مع عمليات الإضافة والحذف المتكررة بكفاءة.

نصيحة احترافية: استخدم خريطة للبيانات الديناميكية التي تتغير بشكل متكرر. بالنسبة للبيانات الثابتة وغير المتغيرة، يكون الكائن أفضل. (جحر الأرنب 1)


عندما تتلقى نقطة النهاية الخاصة بك طلبًا، فإنها تستخرج عنوان IP الخاص بالمستخدم وتستشير Throttler لتحديد ما إذا كان ينبغي السماح بالطلب.

كيف يعمل

  • الحالة أ: مستخدم جديد أو غير نشط

    إذا لم يتم العثور على عنوان IP في Throttler، فهذا إما أن يكون الطلب الأول للمستخدم أو أنه ظل غير نشط لفترة كافية. في هذه الحالة:

    • السماح بالإجراء.
    • تتبع المستخدم عن طريق تخزين عنوان IP الخاص به مع انتهاء المهلة الأولية.
  • الحالة ب: مستخدم نشط

    إذا تم العثور على IP، فهذا يعني أن المستخدم قد قدم طلبات سابقة. هنا:

    • تحقق مما إذا كان وقت الانتظار المطلوب (استنادًا إلى مصفوفة timeoutSeconds) قد مر منذ الكتلة الأخيرة.
    • إذا مر وقت كافي:
    • تحديث الطابع الزمني.
    • زيادة فهرس المهلة (محدد حتى الفهرس الأخير لمنع التجاوز).
    • إذا لم يكن الأمر كذلك، قم برفض الطلب.

في هذه الحالة الأخيرة، نحتاج إلى التحقق مما إذا كان قد مر وقت كافٍ منذ آخر كتلة. نحن نعرف أي من الثواني التي يجب أن نشير إليها بفضل الفهرس. إذا لم يكن الأمر كذلك، ببساطة ترتد مرة أخرى. وإلا قم بتحديث الطابع الزمني.

export class Throttler {
    // ...

    public consume(key: string): boolean {
        const counter = this.storage.get(key) ?? null;
        const now = Date.now();

        // Case A
        if (counter === null) {
            // At next request, will be found.
            // The index 0 of [1, 2, 4, 8, 16] returns 1.
            // That's the amount of seconds it will have to wait.
            this.storage.set(key, {
                index: 0,
                updatedAt: now
            });
            return true; // allowed
        }

        // Case B
        const timeoutMs = this.timeoutSeconds[counter.index] * 1000;
        const allowed = now - counter.updatedAt >= timeoutMs;
        if (!allowed) {
            return false; // denied
        }

        // Allow the call, but increment timeout for following requests.
        counter.updatedAt = now;
        counter.index = Math.min(counter.index   1, this.timeoutSeconds.length - 1);
        this.storage.set(key, counter);

        return true; // allowed
    }
}

عند تحديث الفهرس، يتم تحديده حتى الفهرس الأخير للمهلة بالثواني. بدونه، فإن counter.index 1 سوف يتجاوزه، وبعد ذلك سيؤدي الوصول إلى this.timeoutSeconds[counter.index] إلى حدوث خطأ في وقت التشغيل.

مثال نقطة النهاية

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

const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 300]);

export async function GET({ getClientAddress }) {
    const IP = getClientAddress();

    if (!throttler.consume(IP)) {
        throw error(429, { message: 'Too Many Requests' });
    }

    // Read from DB, call OpenAI - do the thing.

    return new Response(null, { status: 200 });
}

Throttling Explained: A Guide to Managing API Request Limits

ملاحظة للمصادقة

عند استخدام تحديد المعدل مع أنظمة تسجيل الدخول، قد تواجه هذه المشكلة:

  1. يقوم المستخدم بتسجيل الدخول، مما يؤدي إلى قيام Throttler بربط المهلة مع عنوان IP الخاص به.
  2. يقوم المستخدم بتسجيل الخروج أو تنتهي الجلسة (على سبيل المثال، تسجيل الخروج على الفور، وتنتهي صلاحية ملف تعريف الارتباط مع تعطل الجلسة والمتصفحات، وما إلى ذلك).
  3. عندما يحاولون تسجيل الدخول مرة أخرى بعد فترة وجيزة، قد يستمر Throttler في حظرهم، مما يؤدي إلى ظهور خطأ 429 طلبات كثيرة جدًا.

لمنع ذلك، استخدم معرف المستخدم الفريد للمستخدم بدلاً من عنوان IP الخاص به لتحديد المعدل. ويجب عليك أيضًا إعادة ضبط حالة الخانق بعد تسجيل الدخول الناجح لتجنب عمليات الحظر غير الضرورية.

إضافة طريقة إعادة تعيين إلى فئة Throttler:

export class Throttler {
    // ...

    public reset(key: string): void {
        this.storage.delete(key);
    }
}

واستخدامه بعد تسجيل الدخول الناجح:

const user = db.get(email);

if (!throttler.consume(user.ID)) {
    throw error(429);
}

const validPassword = verifyPassword(user.password, providedPassword);
if (!validPassword) {
    throw error(401);
}

throttler.reset(user.id); // Clear throttling for the user

إدارة سجلات IP القديمة من خلال التنظيف الدوري

بينما يقوم جهاز التحكم الخاص بك بتتبع عناوين IP وحدود الأسعار، فمن المهم التفكير في كيفية ووقت إزالة سجلات IP التي لم تعد هناك حاجة إليها. بدون آلية التنظيف، سيستمر الخانق في تخزين السجلات في الذاكرة، مما قد يؤدي إلى مشكلات في الأداء بمرور الوقت مع نمو البيانات.

لمنع ذلك، يمكنك تنفيذ وظيفة التنظيف التي تقوم بشكل دوري بإزالة السجلات القديمة بعد فترة معينة من عدم النشاط. فيما يلي مثال لكيفية إضافة طريقة تنظيف بسيطة لإزالة الإدخالات القديمة من دواسة الوقود.

export class Throttler {
    // ...

    public cleanup(): void {
        const now = Date.now()

        // Capture the keys first to avoid issues during iteration (we use .delete)
        const keys = Array.from(this.storage.keys())

        for (const key of keys) {
            const counter = this.storage.get(key)
            if (!counter) {
                // Skip if the counter is already deleted (handles concurrency issues)
                return
            }

            // If the IP is at the first timeout, remove it from storage
            if (counter.index == 0) {
                this.storage.delete(key)
                continue
            }

            // Otherwise, reduce the timeout index and update the timestamp
            counter.index -= 1
            counter.updatedAt = now
            this.storage.set(key, counter)
        }
    }
}

هناك طريقة بسيطة جدًا (لكنها على الأرجح ليست الأفضل) لجدولة عملية التنظيف باستخدام setInterval:

const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 300])
const oneMinute = 60_000
setInterval(() => throttler.cleanup(), oneMinute)

تساعد آلية التنظيف هذه على ضمان عدم احتفاظ دواسة الوقود بالسجلات القديمة إلى أجل غير مسمى، مما يحافظ على كفاءة تطبيقك. على الرغم من أن هذا النهج بسيط وسهل التنفيذ، إلا أنه قد يحتاج إلى مزيد من التحسين لحالات الاستخدام الأكثر تعقيدًا (على سبيل المثال، استخدام جدولة أكثر تقدمًا أو التعامل مع التزامن العالي).

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


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

بيان الافراج تم إعادة نشر هذه المقالة على: https://dev.to/didof/throttling-explained-a-guide-to-managing-api-request-limits-102a?1 إذا كان هناك أي انتهاك، يرجى الاتصال بـ [email protected] لحذفه
أحدث البرنامج التعليمي أكثر>

تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.

Copyright© 2022 湘ICP备2022001581号-3