برنامه نویسی

Rate Limiter چیست و چرا از آن استفاده می کنیم؟

در سیستم‌های وب، معمولاً یک کلاینت (خواه کاربر یا یک سرویس) درخواست‌های متعددی از یک سرور در مدت زمان کوتاهی ارائه می‌کند. بسته به حجم ترافیک، این می تواند منجر به اضافه بار سرور، پردازش کند و حتی در سیستم هایی شود که قادر به رسیدگی به این تعداد درخواست نیستند.

محدود کننده نرخ تکنیکی است که برای کنترل تعداد درخواست‌هایی که یک کلاینت می‌تواند به یک سرور در طول یک دوره معین انجام دهد استفاده می‌شود. به عنوان یک “دروازه بان” عمل می کند و تعداد تماس ها را به یک API یا سرویس در یک پنجره زمانی محدود می کند.

چرا از Rate Limiter استفاده کنیم؟

استفاده از Rate Limiter در یک برنامه کاربردی به چند دلیل عمل خوبی است:

1 – حفاظت از سوء استفاده: مشتری را از درخواست های بیش از حد یا مخرب مانند حملات انکار سرویس (DoS) که می تواند سیستم را بیش از حد بارگذاری کند، جلوگیری می کند.

2 – تراز مصرف منابع: محدود کردن تعداد درخواست ها کمک می کند تا اطمینان حاصل شود که از منابع سرور به طور موثر استفاده می شود و سایر کاربران آسیب نمی بینند.

3 – عملکرد را بهبود می بخشد: با محدود کردن درخواست‌ها، می‌توانید اطمینان حاصل کنید که برنامه حتی با تعداد دسترسی‌های همزمان زیاد به عملکرد روان ادامه می‌دهد.

4 – تجربه کاربری: با محدود کردن ترافیک درخواست بیش از حد، از مسدود شدن کاربران یا تجربه کندی در برنامه جلوگیری می کنید.

5- امنیت: به جلوگیری از حملات brute force یا تلاش برای سوء استفاده از آسیب پذیری ها با محدود کردن تعداد درخواست ها در ثانیه کمک می کند.

مثال عملی با استفاده از Rate Limiter در Go with Chi

حال، بیایید نمونه‌ای از پیاده‌سازی Rate Limiter را در برنامه Go با استفاده از بسته chi، که به طور گسترده برای مدیریت مسیریابی استفاده می‌شود، تحلیل کنیم.

ما از بسته golang.org/x/time/rate استفاده خواهیم کرد که ابزارهایی را برای ایجاد و مدیریت محدود کننده های نرخ بر اساس الگوریتم Token Bucket فراهم می کند.

کد زیر نحوه پیاده سازی این قابلیت را در برنامه ای که از Go's استفاده می کند نشان می دهد http.Handler برای کنترل تعداد درخواست های ارائه شده توسط مشتری.

package main

import (
    "encoding/json"
    "net/http"
    "strings"
    "time"

    "github.com/go-chi/chi"
    "golang.org/x/time/rate"
)

func main() {
    r := chi.NewRouter()

    // Applies the RateLimiter globally, limiting 5 requests per second and a burst of 10 requests
    r.Use(RateLimiter(rate.Limit(5), 10, 1*time.Second))

    // Define a route for testing
    r.Get("https://dev.to/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Requisição bem-sucedida"))
    })

    http.ListenAndServe(":3000", r)
}

// RateLimiter middleware to limit a client's requests
func RateLimiter(limit rate.Limit, burst int, waitTime time.Duration) func(next http.Handler) http.Handler {
    limiterMap := make(map[string]*rate.Limiter)
    lastRequestMap := make(map[string]time.Time)

    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Capture the client IP
      ip := strings.Split(r.RemoteAddr, ":")[0]

            // Checks if the IP already has a Limiter configured
            limiter, exists := limiterMap[ip]
            if !exists {
                limiter = rate.NewLimiter(limit, burst)
                limiterMap[ip] = limiter
            }

            // Check if the client made a recent request and wait for the timeout if necessary
            lastRequestTime, lastRequestExists := lastRequestMap[ip]
            if lastRequestExists && time.Since(lastRequestTime) < waitTime {
                w.Header().Set("Content-Type", "application/json")
                w.WriteHeader(http.StatusTooManyRequests)
                json.NewEncoder(w).Encode(map[string]string{"error": "Too many requests"})
                return
            }

            // Check if the limit has been reached
            if !limiter.Allow() {
                lastRequestMap[ip] = time.Now()
                w.Header().Set("Content-Type", "application/json")
                w.WriteHeader(http.StatusTooManyRequests)
                json.NewEncoder(w).Encode(map[string]string{"error": "Too many requests"})
            }

            // If everything is ok, move on to the next handler
            next.ServeHTTP(w, r)
        })
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

کد چگونه کار می کند؟

1 – نقشه برداری IP و محدودیت: کد از دو نقشه استفاده می کند: یکی برای ذخیره محدود کننده ها (limiterMap) و دیگری برای ثبت آخرین درخواست ارائه شده توسط یک IP (lastRequestMap). IP مشتری از r.RemoteAddr متغیر

2 – درخواست کنترل: برای هر درخواست، بررسی می شود که آیا IP قبلاً یک محدود کننده پیکربندی شده است یا خیر. اگر این کار را نکرد، یک مورد جدید با حد تعریف شده ایجاد می شود و انفجار می کند.

3 – بررسی کنید: قبل از اجازه دادن به یک درخواست، کد بررسی می کند که آیا مشتری در تلاش است در یک بازه زمانی مشخص درخواست جدیدی ارائه دهد. اگر زمان کافی نباشد، a را برمی گرداند 429 (درخواست های خیلی زیاد) خطا

4 – محدودیت درخواست: rate.Limiter.Allow() روشی است که بررسی می کند آیا مشتری می تواند درخواست را ارائه دهد یا به حد مجاز رسیده است. در صورت رسیدن به حد مجاز، درخواست مسدود شده و خطای 429 برگردانده می شود.

  • انفجار: این حداکثر تعداد درخواست‌هایی است که مشتری می‌تواند فوراً بدون نیاز به منتظر ماندن برای تکمیل محدودیت انجام دهد. این یک “تحمل” برای افزایش ترافیک بالاتر از نرخ معمولی پیکربندی شده در Rate Limiter تعریف می کند.

هنگامی که سعی می کنیم در مدت زمان کوتاهی درخواست های زیادی را انجام دهیم، این خطا را دریافت می کنیم:

محدود کننده نرخ

چه چیزی را می توان در Rate Limiter استفاده کرد؟

Rate Limiting تکنیکی است که می تواند به روش های مختلفی برای شناسایی و محدود کردن استفاده مشتری استفاده شود. در مورد ما، ما از آدرس IP مشتری به عنوان کلیدی برای پیگیری درخواست‌ها و اعمال محدودیت‌ها استفاده می‌کنیم. با این حال، بسته به نوع کاربرد و سناریوی استفاده، رویکردهای دیگری نیز وجود دارد. در اینجا برخی از احتمالات وجود دارد:

1 – توسط IP (مانند مثال ما):

  • ایده آل برای محدود کردن درخواست های ارسال شده از آدرس های خاص. در API های عمومی یا در برنامه هایی که توسط طیف گسترده ای از کلاینت ها قابل دسترسی هستند مفید است.
  • مزیت: پیاده سازی ساده و موثر در برابر سوء استفاده از همان آدرس.
  • محدودیت: در شبکه های مشترک (مانند NAT) که چندین کاربر یک IP مشترک دارند، به خوبی کار نمی کند.

2 – با رمز احراز هویت:

  • مفید برای API هایی که کاربران با توکن ها (JWT، OAuth و غیره) احراز هویت می شوند. در این حالت، شما درخواست ها را بر اساس توکن ارسال شده توسط مشتری پیگیری می کنید.
  • مزیت: جزئیات بیشتر و می تواند کاربران را حتی در شبکه های مشترک متمایز کند.
  • محدودیت: به برنامه نیاز دارد تا از احراز هویت پشتیبانی کند.

3 – توسط شناسه مشتری:

  • رایج در APIهایی که کلیدهای دسترسی را برای هر کلاینت ارائه می کنند (مانند کلیدهای API).
  • مزیت: در سناریوهایی که یکپارچگی بین خدمات وجود دارد و هر مشتری با یک کلید منحصر به فرد شناسایی می شود، به خوبی کار می کند.
  • محدودیت: در صورت اشتراک گذاری یا افشای کلید، از سوء استفاده جلوگیری نمی کند.

4 – هر جلسه کاربر:

  • در برنامه‌های دارای جلسات (کوکی‌ها یا نشانه‌های جلسه)، از شناسه جلسه منحصربه‌فرد می‌توان برای محدود کردن درخواست‌ها استفاده کرد.
  • مزیت: تمرکز بر تجربیات فردی در برنامه.
  • محدودیت: نیاز به مدیریت جلسات و شناسه‌های ذخیره‌سازی.

5 – توسط مسیر یا نقطه پایانی:

1 – برای محدود کردن تماس ها به نقاط پایانی با بار بالا (مانند جستجوها یا آپلودها) مفید است. می تواند با استراتژی های دیگر مانند IP یا توکن ترکیب شود.
2 – مزیت: از نقاط پایانی بحرانی در برابر سوء استفاده محافظت می کند.
4- محدودیت: نیاز به پیکربندی دانه بندی برای هر مسیر دارد.

راه های دیگری برای محدود کردن وجود دارد.

در مثال کد، از آدرس IP (r.RemoteAddr) زیرا این یک رویکرد ساده و کارآمد برای سناریوهای API عمومی یا برنامه هایی است که در آن شناسایی مشتری با توکن ها یا جلسات انجام نمی شود.

ملاحظات نهایی

پیاده سازی Rate Limiter یک راه موثر برای اطمینان از امنیت و ثبات برنامه شما و همچنین ارائه یک تجربه کاربری متعادل تر است. در مثال بالا، نحوه ادغام آن را برای محدود کردن درخواست های مشتری به طور موثر نشان دادیم.
این یک تکنیک ضروری برای محافظت از خدمات شما در برابر سوء استفاده، حملات و اطمینان از توزیع مناسب ترافیک است.

پیوندها

پست وبلاگ من را اینجا ببینید

مخزن نمونه

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا