لبنات البيانات الموزّعة

صمّم محدّد معدل الطلبات

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

الفخّ

تحدّد المعدل لثلاثة أسباب: حماية الخدمة من الحمل الزائد، وإنصاف العملاء فيما بينهم، وضبط التكلفة حين تكون كل طلبية تحرق مالاً. ونفّذ التحديد عند API gateway أو middleware — وليس في العميل أبداً. قيود جهة العميل مجرد اقتراح؛ وكل ما يتحكم فيه المستخدم يستطيع تجاوزه.

الخوارزميات، مقارنةً بصدق
  1. عدّاد النافذة الثابتة: عُدّ الطلبات لكل مستخدم في نافذة دقيقة واحدة. بسيط للغاية — لكنه يندفع عند الحواف: يستطيع العميل إطلاق ضعف الحد بضرب نهاية نافذة وبداية التالية.
  2. سجل أو عدّاد النافذة المنزلقة: تتبّع الطوابع الزمنية (أو امزج نافذتين متجاورتين) بحيث تتحرك النافذة مع الزمن. دقيق عند الحواف — لكنه أثقل: ذاكرة أكثر وحساب أكثر لكل طلب.
  3. دلو الرموز (token bucket): دلو يمتلئ بالرموز بمعدل ثابت، وكل طلب يستهلك رمزاً. الدلو الممتلئ يسمح بدفعة سلسة من الطلبات، ثم يتولى معدل التعبئة القيادة. هذا ما تستخدمه معظم الـAPIs الحقيقية.
  4. الدلو المثقوب (leaky bucket): الطلبات تدخل طابوراً وتخرج بمعدل ثابت، كماء يسيل من ثقب. الدفقات تُنعَّم إلى تدفق خارج منتظم تماماً — ممتاز عندما يتطلب ما خلفك إيقاعاً ثابتاً.
دلو الرموز مقابل الدلو المثقوب: راقب المخرجات لا المدخلات

كلا الدلوين يخنق التدفق، لكن شكل المخرجات مختلف. دلو الرموز (token bucket) يسمح بمرور دفقة — حتى حجم الدلو — ثم يخنق عند معدل التعبئة: التدفق الخارج متموّج بمتوسط محدود. أما الدلو المثقوب (leaky bucket) فيخرج تياراً ثابتاً تماماً مهما كان المدخل خشناً؛ والطلبات الزائدة تنتظر في الطابور أو تُسقَط. اختر بحسب ما يتحمله ما بعدك: بوابة مدفوعات تختنق فوق 50 عملية في الثانية تريد الدلو المثقوب؛ وخدمة بحث تمتص الدفقات تريد دلو الرموز. في المقابلات، سمِّ الشكل الذي تحتاجه، لا اسم الخوارزمية فقط.

عدّاد النافذة المنزلقة: قريب السجل الرخيص

سجل النافذة المنزلقة دقيق لكنه يخزن طابعاً زمنياً لكل طلب — مكلف عند ملايين الطلبات في الثانية. أما عدّاد النافذة المنزلقة فيقرّبه: احتفظ بعدّادين للنافذة الحالية والسابقة، ثم اجعل وزن النافذة السابقة بقدر تداخلها مع المدى المتحرك. إن كانت النافذة 60 ثانية وأنت في الثانية 15 منها، فالتقدير = الحالية + السابقة × (45/60). تحصل على ذاكرة النافذة الثابتة بدقة قريبة من السجل — وقد ذكرت Cloudflare أن هذا خفّض خطأ حواف النافذة الثابتة إلى بضعة بالمئة. الثمن: أنه يفترض توزع الطلبات بالتساوي داخل النافذة السابقة، وهو ما تكذبه دفقة مركّزة.

القواعد والردود ومشكلة التوزيع

القواعد تبدو مثل «100 طلب في الثانية لكل API key» أو «5 محاولات دخول في الدقيقة لكل IP» — اختر الهوية (مستخدم، IP، مفتاح) التي تناسب الإساءة التي تخشاها. وعندما يتجاوز العميل الحد، أجب بـ 429 Too Many Requests مع ترويسة Retry-After ليتراجع العملاء الملتزمون بلباقة. والآن الجزء الصعب: مع عشرة خوادم تحديد، كل منها يرى عُشر الزيارات فقط — العدّ المحلي يقلّل العدد بالتصميم. الحل المعياري هو حالة مشتركة في مخزن مركزي سريع (مثل Redis)، مع زيادات ذرّية (atomic) لتجاوز حالات التسابق عندما يحكم خادمان على الطلب نفسه في اللحظة نفسها.

أين تسكن القواعد — وماذا لو تعطل مخزنها

القواعد نفسها لا ينبغي أن تكون ثابتة في الشيفرة. خدمة إعدادات (config service) تحتفظ بها — حصص لكل endpoint ومستويات لكل خطة — وأسطول المحدّدات يسحبها ويخزنها محلياً، فرفع حدٍّ ما ينتشر خلال ثوانٍ دون نشر جديد (deploy). والآن سؤال الفشل: مخزن الإعدادات معطل والمحدّد لا يستطيع التحديث. أن تُقفل على الفشل (fail closed) — أي تستمر بتطبيق آخر قواعد مخزنة — فقد ترفض زيارات سليمة بسبب حدٍّ قديم. وأن تفتح على الفشل (fail open) — أي تمرّر كل شيء — فتفقد الحماية تماماً عندما تكون أنظمتك تحت الضغط. حل وسط شائع: خزّن القواعد بـTTL طويل، أُقفل على الفشل ما دامت الذاكرة المحلية قائمة، وافتح فقط عندما لا يوجد شيء مخزن أصلاً. اكتب القرار مسبقاً، لا أثناء العطل.

جرّب بنفسك
المبدأ

محدّد المعدل صمّام: أنت تقرر طلب من الذي سيموت ليحيا النظام. كل قاعدة تكتبها هي سياسة عدالة متنكرة في هيئة خوارزمية.

تحقّق سريع

لماذا تسمح النافذة الثابتة للعميل بالحصول لحظياً على ضعف حصته — وأي خوارزميتين تعالجان هذه الثغرة، وبأي ثمن؟ (حدّ النافذة يصفّر العدّاد، فتتراكم دفقة نهاية النافذة مع دفعة بداية التالية؛ النافذة المنزلقة تعالجها بذاكرة أكبر لكل مستخدم، ودلو الرموز يعالجها بحساب معدل التعبئة وسماحية للدفقات.)

الخلاصة

حدّد عند الـgateway، لا عند العميل. اختر الخوارزمية للسلوك الذي تريده — دلو الرموز لدفقات سلسة، الدلو المثقوب لتدفق ثابت، النافذة الثابتة حين تتفوق البساطة على الكمال — وأجب بـ429 مع Retry-After، وحلّ مشكلة العدّ الموزّع بحالة مشتركة ذرّية.

📌 افعل هذا الاثنين

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

لبنات البيانات الموزّعة