تحديد المعدل (Rate Limiting) والتحكم في التدفق (Throttling) في C#: كيف تحمي API الخاص بك من الأحمال الزائدة وتحافظ على راحة المستخدمين
\n\nتخيل: لقد أطلقت API رائعًا. الخادم يعمل بسرعة، وقاعدة البيانات تستجيب فورًا. ولكن فجأة - بام! - عميل غير مسؤول (أو ما هو أسوأ، هجوم DDoS) يبدأ في قصف نقطة الدخول الخاصة بك بآلاف الطلبات في الثانية. ينهار الخادم، وتختنق قاعدة البيانات، ويرى المستخدمون الحقيقيون خطأ 503. هل هذا مألوف؟
\n\nحل هذه المشكلة هو تحديد المعدل (Rate Limiting) والتحكم في التدفق (Throttling). في C#، لدينا جميع الأدوات لتطبيق هذه الآليات بأناقة وفعالية. في هذه المقالة، لن نقتصر على النظرية فقط - بل سنكتب كودًا حقيقيًا، ونناقش الخوارزميات الشائعة، ونكتشف ما نختاره لمهمة محددة. استعدوا، سيكون الأمر مثيرًا!
\n\nتحديد المعدل (Rate Limiting) مقابل التحكم في التدفق (Throttling): ما الفرق؟
\n\nعلى الرغم من أن هذه المصطلحات غالبًا ما يتم الخلط بينها، إلا أن هناك خطًا واضحًا بينها:
\n- \n
- تحديد المعدل (Rate Limiting) - هو حظر "صارم". إذا تجاوز المستخدم الحد المسموح (مثل 100 طلب في الدقيقة)، يقوم الخادم بإرجاع HTTP 429 (طلبات كثيرة جدًا). الطلبات ببساطة لا تصل إلى منطق الأعمال. \n
- التحكم في التدفق (Throttling) - هو إبطاء "ناعم". يقبل الخادم الطلب، لكنه يعالجه بتأخير أو يخفض أولويته. لا يتلقى المستخدم خطأ، لكن سرعة عمله تنخفض. \n
في C#، يمكننا تنفيذ كلا النهجين، وغالبًا ما نجمعهما لسيناريوهات مختلفة.
\n\nخوارزميات تحديد المعدل (Rate Limiting): من البسيط إلى المعقد
\n\nقبل كتابة الكود، دعنا نفهم الرياضيات. هناك أربع خوارزميات "ذهبية" تشكل أساس 90% من الحلول.
\n\n1. النافذة الثابتة (Fixed Window)
\nالطريقة الأبسط. نقسم الوقت إلى فترات زمنية ثابتة (مثل دقيقة واحدة) ونحسب الطلبات في كل نافذة. بمجرد أن يصل العداد إلى الحد المسموح - نمنع.
\nالإيجابيات: سهولة التنفيذ. السلبيات: مشكلة "تأثير الحافة" (اندفاع عند الانتقال بين النوافذ).
\n\npublic class FixedWindowRateLimiter\n{\n private readonly int _limit;\n private readonly TimeSpan _windowSize;\n private int _counter;\n private DateTime _windowStart;\n\n public FixedWindowRateLimiter(int limit, TimeSpan windowSize)\n {\n _limit = limit;\n _windowSize = windowSize;\n _windowStart = DateTime.UtcNow;\n }\n\n public bool AllowRequest()\n {\n var now = DateTime.UtcNow;\n if (now - _windowStart >= _windowSize)\n {\n // إعادة تعيين النافذة\n _counter = 0;\n _windowStart = now;\n }\n\n if (_counter >= _limit)\n return false; // طلبات كثيرة جدًا\n\n Interlocked.Increment(ref _counter);\n return true;\n }\n}\n\n2. النافذة المنزلقة (Sliding Window)
\nتطوير للنافذة الثابتة. بدلاً من الحدود الصارمة، نأخذ في الاعتبار الطلبات في فترة زمنية منزلقة. عادةً ما يتم تنفيذها من خلال تسجيل الطوابع الزمنية (على سبيل المثال، في مخزن مؤقت دائري).
\nالإيجابيات: تخفف التدفقات المفاجئة. السلبيات: أكثر تعقيدًا قليلاً في التنفيذ، وتتطلب ذاكرة لتخزين الطوابع.
\n\npublic class SlidingWindowRateLimiter\n{\n private readonly int _limit;\n private readonly TimeSpan _windowSize;\n private readonly Queue<DateTime> _timestamps = new();\n\n public SlidingWindowRateLimiter(int limit, TimeSpan windowSize)\n {\n _limit = limit;\n _windowSize = windowSize;\n }\n\n public bool AllowRequest()\n {\n var now = DateTime.UtcNow;\n lock (_timestamps)\n {\n // إزالة الطوابع القديمة\n while (_timestamps.Count > 0 && now - _timestamps.Peek() > _windowSize)\n _timestamps.Dequeue();\n\n if (_timestamps.Count >= _limit)\n return false;\n\n _timestamps.Enqueue(now);\n return true