بالنسبة للمشاريع الكبيرة، من الأفضل عادةً استخدام أدوات مثل Cloudflare Rate Limiting أو HAProxy. إنها قوية وموثوقة وتتولى رفع الأحمال الثقيلة نيابةً عنك.
ولكن بالنسبة للمشاريع الصغيرة - أو إذا كنت تريد معرفة كيفية عمل الأشياء - يمكنك إنشاء محدد المعدل الخاص بك مباشرة في التعليمات البرمجية الخاصة بك. لماذا؟
بنهاية هذا الدليل، ستعرف كيفية إنشاء أداة خانقة أساسية في TypeScript لحماية واجهات برمجة التطبيقات الخاصة بك من الإرهاق. إليك ما سنغطيه:
تم تصميم هذا الدليل ليكون نقطة بداية عملية، ومثاليًا للمطورين الذين يرغبون في تعلم الأساسيات دون تعقيدات غير ضرورية. لكنها ليست جاهزة للإنتاج.
قبل البدء، أريد منح الاعتمادات الصحيحة لقسم تحديد الأسعار في لوسيا.
دعونا نحدد فئة 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، فهذا يعني أن المستخدم قد قدم طلبات سابقة. هنا:
في هذه الحالة الأخيرة، نحتاج إلى التحقق مما إذا كان قد مر وقت كافٍ منذ آخر كتلة. نحن نعرف أي من الثواني التي يجب أن نشير إليها بفضل الفهرس. إذا لم يكن الأمر كذلك، ببساطة ترتد مرة أخرى. وإلا قم بتحديث الطابع الزمني.
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 }); }
عند استخدام تحديد المعدل مع أنظمة تسجيل الدخول، قد تواجه هذه المشكلة:
لمنع ذلك، استخدم معرف المستخدم الفريد للمستخدم بدلاً من عنوان 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 التي لم تعد هناك حاجة إليها. بدون آلية التنظيف، سيستمر الخانق في تخزين السجلات في الذاكرة، مما قد يؤدي إلى مشكلات في الأداء بمرور الوقت مع نمو البيانات.
لمنع ذلك، يمكنك تنفيذ وظيفة التنظيف التي تقوم بشكل دوري بإزالة السجلات القديمة بعد فترة معينة من عدم النشاط. فيما يلي مثال لكيفية إضافة طريقة تنظيف بسيطة لإزالة الإدخالات القديمة من دواسة الوقود.
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)
تساعد آلية التنظيف هذه على ضمان عدم احتفاظ دواسة الوقود بالسجلات القديمة إلى أجل غير مسمى، مما يحافظ على كفاءة تطبيقك. على الرغم من أن هذا النهج بسيط وسهل التنفيذ، إلا أنه قد يحتاج إلى مزيد من التحسين لحالات الاستخدام الأكثر تعقيدًا (على سبيل المثال، استخدام جدولة أكثر تقدمًا أو التعامل مع التزامن العالي).
من خلال التنظيف الدوري، يمكنك منع تضخم الذاكرة والتأكد من عدم تعقب المستخدمين الذين لم يحاولوا تقديم طلبات منذ فترة - وهذه هي الخطوة الأولى نحو جعل نظام تحديد المعدل الخاص بك قابلاً للتطوير وفعالاً في استخدام الموارد.
إذا كنت تشعر بالمغامرة، فقد تكون مهتمًا بقراءة كيفية تخصيص الخصائص وكيف تتغير. أيضًا، لماذا لا، فيما يتعلق بتحسينات الأجهزة الافتراضية مثل ذاكرة التخزين المؤقت المضمنة، والتي يفضلها بشكل خاص الشكل الأحادي. يتمتع. ↩
تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.
Copyright© 2022 湘ICP备2022001581号-3