System Design

تسلط بر محدودیت نرخ: مهندسی سیستم‌های مقاوم و مقیاس‌پذیر

در فضای سیستم‌های توزیع‌شده مدرن، اطمینان از در دسترس بودن و قابلیت اطمینان حیاتی است. در میان چالش‌های متعدد معماران سیستم، محافظت از سرویس‌ها در برابر سوءاستفاده، افزایش ترافیک و حملات رد سرویس (DoS) برجسته است. محدودیت نرخ استراتژی بنیادین برای مدیریت مصرف منابع و اطمینان از عملکرد سیستم در شرایط نامساعد است. این پست به بررسی مفاهیم کلیدی، الگوریتم‌ها و پیاده‌سازی‌های عملی محدودیت نرخ در طراحی سیستم می‌پردازد.

چرا محدودیت نرخ مهم است

محدودیت نرخ تنها یک اقدام امنیتی نیست؛ بلکه یک مکانیسم کیفیت سرویس (QoS) است. بدون آن، یک مشتری بدرفتار یا یک افزایش ناگهانی ترافیک ویروسی می‌تواند منابع CPU، حافظه یا اتصالات پایگاه داده را مستهلک کند و عملکرد را برای همه کاربران کاهش دهد. با محدود کردن تعداد درخواست‌هایی که یک مشتری می‌تواند در یک بازه زمانی خاص ارسال کند، شما:

  • از سوءاستفاده جلوگیری می‌کنید: جلوی اقدامات مخرب مانند استخراج داده‌ها (Scraping) یا حملات حدسی به رمزهای عبور را می‌گیرید.
  • عدالت را تضمین می‌کنید: اطمینان حاصل می‌کنید که هیچ کاربری به تنهایی منابع سیستم را انحصاری نکند.
  • هزینه‌ها را مدیریت می‌کنید: بار محاسباتی را محدود می‌کنید که برای معماری‌های سرورلس یا پرداخت به ازای استفاده حیاتی است.
  • تخریب تدریجی (Graceful Degradation) ارائه می‌دهید: خطاهای معنادار (مانند HTTP 429 Too Many Requests) را باز می‌گردانید به جای اینکه باعث کرش کردن سیستم شوید.

الگوریتم‌های رایج محدودیت نرخ

انتخاب الگوریتم مناسب به الگوهای ترافیک شما و تحمل شما برای نوسانات (Burstiness) بستگی دارد. سه رویکرد رایج‌تر عبارتند از: شمارنده پنجره ثابت (Fixed Window Counter)، لاگ پنجره لغزان (Sliding Window Log) و سطل توکن (Token Bucket).

شمارنده پنجره ثابت ساده‌ترین روش برای پیاده‌سازی است اما از مشکل «مرز» رنج می‌برد، جایی که درخواست‌های مجاز در انتهای یک پنجره و ابتدای پنجره بعدی می‌توانند دو برابر حد مجاز باشند. برای کنترل نرم‌تر، لاگ پنجره لغزان زمان‌مهر هر درخواست را ردیابی می‌کند که دقت را در هزینه استفاده بیشتر از حافظه ارائه می‌دهد. با این حال، الگوریتم سطل توکن اغلب به دلیل تعادل بین سادگی و انعطاف‌پذیری، انتخاب مورد علاقه است. این الگوریتم اجازه می‌دهد تا نوسانات کوتاه‌مدت ترافیک رخ دهد در حالی که میانگین نرخ بلندمدت را حفظ می‌کند و برای APIهایی که نیاز به مدیریت بار متغیر دارند، ایده‌آل است.

پیاده‌سازی سطل توکن با Redis

در یک محیط توزیع‌شده، وضعیت مشترک (Shared State) حیاتی است. Redis به دلیل سرعت و عملیات اتمی آن، استاندارد غیررسمی برای پیاده‌سازی محدودیت نرخ توزیع‌شده است. در زیر یک مثال پایتون با استفاده از کتابخانه redis-py برای پیاده‌سازی منطق پایه سطل توکن آورده شده است.

import time
import redis

class RateLimiter:
    def __init__(self, redis_client, rate, capacity):
        self.redis = redis_client
        self.rate = rate       # توکن‌های اضافه شده در ثانیه
        self.capacity = capacity # حداکثر توکن‌های مجاز
        self.tokens_key = "rate_limit:"

    def is_allowed(self, client_id):
        key = f"{self.tokens_key}{client_id}"
        now = time.time()
        
        # دریافت تعداد توکن فعلی و زمان آخرین به‌روزرسانی
        pipe = self.redis.pipeline()
        pipe.get(key)
        pipe.execute()
        
        # اگر کلید وجود ندارد، آن را مقداردهی اولیه کنید
        data = self.redis.get(key)
        if data is None:
            self.redis.set(key, self.capacity)
            self.redis.expire(key, 60) # پس از 60 ثانیه منقضی شود تا پاکسازی شود
            return True
            
        current_tokens = float(data)
        
        # افزودن توکن‌های جدید بر اساس زمان سپری شده
        time_passed = time.time() - (self.redis.get(f"{key}:last_access") or now)
        new_tokens = min(self.capacity, current_tokens + (time_passed * self.rate))
        
        if new_tokens >= 1:
            self.redis.set(key, new_tokens - 1)
            self.redis.set(f"{key}:last_access", now)
            return True
        else:
            self.redis.set(key, new_tokens)
            self.redis.set(f"{key}:last_access", now)
            return False

ملاحظات عملی

هنگام پیاده‌سازی محدودیت نرخ، در نظر بگیرید که محدودیت را کجا اعمال کنید. محدودیت نرخ در سطح دروازه (با استفاده از Nginx، Kong یا AWS API Gateway) کارآمد است زیرا ترافیک مخرب را قبل از رسیدن به سرورهای اپلیکیشن متوقف می‌کند. با این حال، محدودیت در سطح اپلیکیشن اجازه می‌دهد تا منطق کسب‌وکار ظریف‌تری، مانند محدودیت‌های لایه‌بندی شده برای کاربران رایگان در مقابل کاربران پولی، اعمال شود.

همیشه محدودیت‌ها را به وضوح ارتباط دهید. از هدرهای استاندارد HTTP مانند X-RateLimit-Limit، X-RateLimit-Remaining و X-RateLimit-Reset برای کمک به مصرف‌کنندگان API در درک استفاده خود استفاده کنید. علاوه بر این، اطمینان حاصل کنید که پاسخ‌های خطای شما شامل هدر Retry-After باشد تا مشتریان بدانند چه زمانی باید مجدداً تلاش کنند.

نتیجه‌گیری

محدودیت نرخ یک جزء ضروری در طراحی سیستم‌های مقاوم است. با درک مبادله‌ها (Trade-offs) بین الگوریتم‌های مختلف و بهره‌گیری از ابزارهایی مانند Redis برای مدیریت وضعیت توزیع‌شده، می‌توانید سیستم‌هایی بسازید که مقاوم، عادلانه و امن باشند. با رشد اپلیکیشن شما، استراتژی‌های محدودیت نرخ خود را به طور منظم بازبینی کنید تا اطمینان حاصل کنید که با الگوهای ترافیک در حال تغییر و اهداف کسب‌وکار شما همسو هستند.

Share: