Rate Limiting چیست و چگونه از API در برابر سوءاستفاده محافظت میکند؟

سامانه تیکتینگ پشتیبانی یک شرکت، صبح شنبه برای همه کاربران کند شده است؛ پاسخها از چند ثانیه به چند ده ثانیه رسیده و صف تیکتهای باز روی مانیتور پشتیبانی جمع شده. هیچ تغییری در کد منتشر نشده و ترافیک عادی هم بیشتر از همیشه نیست. ریشهیابی نشان میدهد یک اسکریپت گزارشگیری که یکی از تیمها برای همگامسازی داشبورد نوشته، هر ثانیه دهها درخواست به API سامانه میفرستد و منابع را به خود اختصاص داده است.
پرسش اصلی این مقاله از همینجا میآید: وقتی منابع سرویس محدود است و رفتار مصرفکنندگان نامساوی، چگونه میتوان بدون بستن دست همه، از فلجشدن سرویس جلوگیری کرد؟ پاسخ رایج مهندسی، محدودسازی نرخ (Rate Limiting) است: گذاشتن سقف منصفانه روی تعداد درخواستها در بازه زمانی. بیایید این مفهوم را تحلیل کنیم و به نتیجهای عملی برای پیادهسازی برسیم.
مفهوم پایه: نرخ، سقف و کلید شمارش
محدودسازی نرخ یعنی سرویس برای هر «کلید» مشخص کند در هر بازه زمانی حداکثر چند درخواست را بپذیرد. کلید میتواند آدرس IP، کاربر احرازشده، کلید API یا ترکیبی از اینها باشد. درخواست مازاد پذیرفته نمیشود و پاسخ روشنی میگیرد؛ در پروتکل HTTP این وضعیت با کد 429 به معنی درخواست بیش از حد شناخته میشود و معمولاً اطلاعاتی درباره «چه زمانی دوباره تلاش کن» همراهش میآید.
دقت کنید هدف، مجازات نیست؛ عدالت در توزیع منابع و پایداری سرویس است. سرویس بدون سقف مثل بانکی است که صندوقدارهایش ماهرند اما هیچ نظارتی بر صف ندارد؛ یک مشتری پرتوقع میتواند همه صندوقدارها را مشغول کند و بقیه در بایگانی معطل بمانند. سقف، همان قانون صف است: همه میآیند، اما هیچکس همه ظرفیت را نمیبرد.
تحلیل: چهار روش رایج شمارش
چالش اصلی عملیاتی این است که «چگونه شمارش کنیم؟». چهار الگوی رایج پاسخهای متفاوتی به این پرسش میدهند و تفاوتشان در رفتار با ترافیک موجدار و هزینه نگهداری است.
- پنجره ثابت (Fixed Window): بازههای زمانی یکسان مثل هر دقیقه در نظر گرفته میشود و شمارنده هر بازه جداست. ساده و ارزان است، اما در مرز دو بازه میتواند تقریباً دو برابر سقف عبور کند: عدهای در پایان دقیقه اول و عدهای در آغاز دقیقه دوم.
- پنجره لغزان (Sliding Window): بهجای بازه ثابت، «تعداد درخواست در N ثانیه گذشته» شمرده میشود؛ دقیقتر است اما نگهداری تاریخچه بیشتری میخواهد.
- سطل توکن (Token Bucket): توکنها با نرخ ثابت در سطل جمع میشوند و هر درخواست یک توکن مصرف میکند؛ انفجار لحظهای (Burst) تا سقف سطل پذیرفته میشود. برای مشتریانی با ترافیک موجدار منصفانهتر است.
- سطل نشتی (Leaky Bucket): درخواستها وارد سطل میشوند و با نرخ ثابت پردازش میشوند؛ موجها صاف میشوند و به سرویس پاییندستی ریتم یکنواخت میرسد.
| روش | رفتار با انفجار لحظهای درخواست | هزینه نگهداری | مناسب برای |
|---|---|---|---|
| پنجره ثابت | در مرز بازهها امکان عبور بیش از سقف | کم | سقفهای ساده ساعتی و روزانه |
| پنجره لغزان | کنترل دقیق در هر لحظه | متوسط تا زیاد | سقفهای حساس و قراردادهای دقیق |
| سطل توکن | پذیرش موج در حد ظرفیت سطل | متوسط | APIهای عمومی با ترافیک موجدار |
| سطل نشتی | صافکردن موج با نرخ ثابت خروج | متوسط | محافظت از سرویسهای پاییندستی حساس |
کجا شمارش انجام شود؟
اگر سرویس روی چند سرور اجرا شود، شمارنده در حافظه هر سرور به درد نمیخورد: هر سرور سقف خودش را میبیند و مجموع میتواند چند برابر سقف واقعی شود. راهحل معمول، شمارش متمرکز در یک حافظه سریع مشترک مثل Redis است که همه نمونههای سرویس به آن نگاه میکنند. در معماریهای بزرگتر، محدودسازی نرخ به «لبه» منتقل میشود: دروازه API (API Gateway) یا لایه توزیعکننده بار، پیش از رسیدن درخواست به سرویسها سقفها را اعمال میکند و سرویسها از شلوغی بیرون دروازه خبر ندارند.
مثال کاربردی: سقفبندی سامانه تیکتینگ
فرض کنید تیم سامانه تیکتینگ تصمیم میگیرد سقفها را لایهبندی کند. کاربران ناشناس سقف کوچکی برای جستوجو دارند؛ کاربران عادی پنل پشتیبانی سقف متوسطی در هر دقیقه؛ و همکاران شرکتی که با کلید API گزارش میگیرند، سقف بالاتر و قابل مذاکره. اسکریپت داشبورد هم که تا دیروز مستقیم به API حمله میکرد، ناگهان در سقف شرکتی میخورد و اینبار خبردار میشود.
اینجا نکته کلیدی ظاهر میشود: درخواست ردشده نباید بیخبر رد شود. پاسخ 429 همراه با سرصفحههای اطلاعرسانی به مشتری میگوید سقف چقدر باقی مانده و چه زمانی دوباره تلاش کند؛ سمت اسکریپت هم منطق عقبنشینی نمایی (Exponential Backoff) اجرا میشود — با هر تلاش ناموفق، فاصله تلاش بعدی بیشتر میشود. نتیجه قابل پیشبینی است: داشبورد با نیمهنرخ به کار ادامه میدهد، کاربران عادی بدون افت سرعت تیکت میزنند و پشتیبانی، سقف شرکتی را مذاکره میکند. هیچکس سرویس را از دست نداده؛ فقط منصفانه صف شده است.
محدودیتها و اشتباههای رایج
- سقف روی IP بهتنهایی: در شبکههای سازمانی و اینترنت همراه، دهها کاربر بیگناه پشت یک آدرس جمع میشوند؛ کلید احرازشده معمولاً منصفانهتر است.
- سقف حدسی: سقف خیلی سختگیرانه مشتریان واقعی را میراند و سقف شل فقط حیلهگران را میگیرد؛ سقف باید از داده ترافیک واقعی آغاز و بهمرور تنظیم شود.
- بیخبرگذاشتن مشتری: سرصفحههای اطلاعرسانی سقف و مستندات روشن، هزینه پشتیبانی را کم میکند؛ سکوت، مشتری را به حدس و تماس وامیدارد.
- ردکردن بدون ثبت: الگوی ردشدنها خودش سرنخ حمله و خطای برنامه است؛ بیثباتش کردن، پایش را نابود میکند.
- انتظار معجزه: این لایه جای احراز هویت، اعتبارسنجی و کش را نمیگیرد؛ فقط یکی از چند لایه حفاظت است.
نتیجه عملی: از کجا شروع کنیم؟
در عمل، مسیر سادهای برای شروع وجود دارد. نخست، نقاط پرهزینه سرویس را مشخص کنید: جستوجو، گزارشگیری و ورود معمولاً گرانترینها هستند. دوم، برای هر نقطه تصمیم بگیرید کلید شمارش چیست: ناشناس، کاربر یا کلید API. سوم، با الگوی سادهای مثل پنجره ثابت شروع کنید و فقط اگر ترافیک موجدار شد به سطل توکن مهاجرت کنید. چهارم، پاسخ ردشدن را انسانی و اطلاعرسان طراحی کنید و سرصفحههای اطلاعرسانی سقف را از روز اول بفرستید. پنجم، ردشدنها را بهعنوان یک شاخص پایش کنید: جهش ناگهانی ردشدنها هم حمله را زودتر نشان میدهد و هم خطای برنامهای مثل همان اسکریپت بیمحابا.
با این پنج گام، صحنه ابتدای مقاله به شکل دیگری تمام میشود: اسکریپت در سقف میخورد، پیام روشن و زمان تلاش دوباره میگیرد، تیم صاحب اسکریپت رفتارش را تنظیم میکند و بقیه کاربران هرگز متوجه ماجرا نمیشوند. این دقیقاً تعریف یک لایه حفاظت موفق است: بهجای عصبانیت انسانی و خاموشکردن دستی، سیاستی قابل پیشبینی که همه بدانند چگونه با آن کنار بیایند.
نکات کلیدی این مقاله
- محدودسازی نرخ سقف منصفانه مصرف منابع مشترک است؛ هدف مجازات نیست، پایداری است.
- انتخاب کلید شمارش — IP، کاربر یا کلید API — به اندازه انتخاب الگوریتم سرنوشتساز است.
- پنجره ثابت سادهترین آغاز است؛ سطل توکن برای ترافیک موجدار منصفانهتر رفتار میکند.
- درخواست ردشده باید اطلاعرسانی روشن با زمان تلاش دوباره بگیرد؛ سمت مشتری هم عقبنشینی نمایی داشته باشد.
- این لایه جایگزین احراز هویت، کش و اعتبارسنجی نیست؛ مکمل آنهاست.
سوالات متداول
کد 429 بهتنهایی کافی است؟
نه؛ کد فقط وضعیت را میگوید. پاسخ خوب همراه با سرصفحههایی است که سقف باقیمانده و زمان مناسب تلاش دوباره را اعلام میکنند و در صورت امکان، یک متن فارسی و خوانا برای کاربر نهایی دارد. مشتری خوب با همین اطلاعات رفتارش را اصلاح میکند و نیازی به تماس با پشتیبانی نیست.
سقف مناسب را از کجا بفهمیم؟
از داده خودتان، نه از حدس. توزیع درخواستهای کاربران عادی را ببینید و سقفی انتخاب کنید که بخش عمده کاربران عادی هرگز به آن برخورد نکنند و فقط رفتارهای ماشینی و پرشتاب متوقف شوند. سقف سند زنده است: با تغییر محصول و مشتریان، بازبینی میشود.
اگر کاربر واقعی به سقف بخورد چه؟
اول تجربه ردشدن را انسانی کنید: پیام روشن، زمان تلاش دوباره و در صورت لزوم مسیر ارتقای سقف برای مشتریان رسمی. دوم الگوی برخورد را پایش کنید؛ اگر کاربران واقعی بهطور مکرر به سقف میخورند، یا سقف غلط تنظیم شده یا نیاز واقعی وجود دارد که پاسخش سقف بیشتر یا طراحی دیگری است، نه حذف کاربر.



