هذا المقال يتطلب مستوى متوسط. لو عندك Nginx قدّام تطبيقك وعميل واحد قدر يخلّي السيرفر بطيء للكل، اللي قدامك هنا هيقفل الباب ده في 8 أسطر إعداد، من غير ما تلمس كود التطبيق.
التحكم في معدل الطلبات (Rate Limiting) على الـ API بـ Nginx
الفكرة بسيطة: تحدّد لكل عميل سقف معيّن من الطلبات في الثانية. اللي يعدّي السقف بياخد رد 429 Too Many Requests بدل ما ياكل موارد السيرفر على حساب باقي المستخدمين. ده بيحميك من البوتات، ومن الـ brute force على صفحة تسجيل الدخول، ومن سكربت عميل اتكتب غلط وبيعمل retry في loop.
المشكلة باختصار
تخيّل API بيخدم 50 ألف مستخدم في اليوم. كل واحد طبيعي بيعمل طلب أو اتنين في الثانية. فجأة بوت واحد بيبدأ يضرب مسار /api/login بمعدل 500 طلب/ثانية. السيرفر مش بيفرّق: بيحاول يخدم الـ 500 زي ما بيخدم أي طلب. النتيجة إن worker connections بتتملي، والـ CPU بيوصل 95%، وزمن استجابة المستخدم العادي بيقفز من 90 ميلي ثانية لأربع ثواني. مفيش خطأ في الكود، بس مفيش حاجة بتقطع النزيف.
الحل مش إنك تكبّر السيرفر. السيرفر الأكبر هياكل نفس الـ 500 طلب أسرع شوية وخلاص. الحل إنك تحطّ بوابة بتقول: لكل IP معدل ثابت، والزيادة بترجع 429.
المفهوم: خوارزمية الدلو المثقوب (Leaky Bucket)
قبل الإعداد، خلّينا نفهم بيشتغل إزاي. تخيّل حارس واقف على باب ملهى صغير. الناس بتيجي على دفعات، أحيانًا 10 مرة واحدة. الحارس بيدخّل واحد كل 6 ثواني بالظبط، مهما كان الزحام. قدّام الباب فيه طابور بيستوعب 20 شخص بس؛ اللي ييجي والطابور مليان بيترفض ويمشي.
دي بالظبط خوارزمية الدلو المثقوب. الطلبات بتنزل في دلو زي نقط الميّة. الدلو بيسرّب بمعدل ثابت (ده الـ rate). الدلو له سعة محدودة لاستيعاب الدفعات المفاجئة (ده الـ burst). أول ما الدلو يفيض، أي طلب زيادة بيترفض بـ 429.
علميًا: Nginx بيخزّن لكل مفتاح (هنا عنوان الـ IP) آخر وقت سُمح فيه بطلب. لما ييجي طلب جديد، بيحسب لو عدّى الوقت الكافي حسب الـ rate. لو لسه بدري، بيشوف لسه فيه مكان في الـ burst ولا لأ. ده بيخلّي القرار ثابت الزمن O(1) ورخيص جدًا على الذاكرة.
الإعداد العملي في Nginx
الإعداد جزئين: تعريف المنطقة (zone) في بلوك http، وتطبيق الحد على المسار اللي عايزه في بلوك location.
# داخل بلوك http { }
# zone بحجم 10 ميجا تتسع لحوالي 160 ألف IP
# المعدل المسموح: 10 طلبات في الثانية لكل IP
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
listen 80;
server_name api.example.com;
location /api/ {
# burst=20: استوعب لحد 20 طلب فجائي زيادة
# nodelay: اسمح بالـ burst فورًا بدل ما تأخّره
limit_req zone=api_limit burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
}