9.4.16 الگو Rate limit

9.4.16 الگو Rate limit

9.4.16.1 توضیحات #

الگوی Rate Limiting یا “محدودسازی نرخ درخواست” یکی از الگوهای پرکاربرد برای کنترل ترافیک ورودی یا پردازش عملیات در سیستم‌های نرم‌افزاری است. هدف اصلی این الگو، جلوگیری از اجرای بیش از حد عملیات در یک بازه‌ی زمانی مشخص است تا از بارگذاری بیش از حد سیستم، نقض محدودیت‌های منابع خارجی (مثل APIها)، یا سوءاستفاده از سرویس جلوگیری شود. این الگو در سرویس‌هایی که به منابع محدود یا خارجی متصل‌اند—مثل وب‌سرویس‌ها، میکروسرویس‌ها، API گیت‌وی‌ها یا سیستم‌های صف پردازش—به‌شدت حیاتی است.

در زبان Go، یکی از ساده‌ترین روش‌های پیاده‌سازی این الگو استفاده از time.Ticker است. این نوعی تایمر است که در فواصل زمانی منظم سیگنالی را از طریق کانال ارسال می‌کند. با قرار دادن منطق پردازش درون حلقه‌ای که روی این کانال می‌چرخد، می‌توان کاری کرد که اجرای هر عملیات فقط هنگام دریافت سیگنال مجاز باشد. مثلاً اگر یک ticker هر ۲۰۰ میلی‌ثانیه سیگنال بفرستد، در نتیجه فقط ۵ بار در ثانیه عملیات اجرا خواهد شد. به این ترتیب نرخ اجرا به شکل دقیق و منظم کنترل می‌شود.

مزیت این روش در سادگی و کارآمدی آن است، به‌خصوص برای سناریوهای سبک تا متوسط. اما در شرایط پیچیده‌تر، ممکن است نیاز به الگوهای پیشرفته‌تری مانند Token Bucket یا Leaky Bucket باشد که انعطاف‌پذیری بیشتری برای burstهای ناگهانی، اولویت‌بندی یا بازتوزیع ظرفیت فراهم می‌کنند. همچنین در سیستم‌های توزیع‌شده، برای اعمال محدودیت به صورت مرکزی یا سراسری (distributed rate limiting)، باید از ابزارهایی مثل Redis، Nginx، یا سرویس‌های ابری (مثل Cloudflare یا AWS API Gateway) استفاده کرد.

در نهایت، الگوی Rate Limiting به عنوان یک تکنیک دفاعی و پایدارسازی سیستم، باید بخشی جدانشدنی از معماری‌ سیستم‌های تولیدی باشد—چه در قالب محدودسازی ساده در سطح goroutineها، و چه به‌صورت سیاست‌های سازمان‌یافته در کل سیستم.

9.4.16.2 دیاگرام #

graph TD subgraph Client A[ارسال درخواست] end subgraph Rate Limiter Ticker B[دریافت تیک از time.Ticker] C{آیا درخواست جدیدی هست؟} end subgraph Worker D[پردازش درخواست] end A -->|درخواست به صف| C B --> C C -->|بله| D C -->|خیر| B D --> B

9.4.16.3 نمونه کد #

 1package main
 2
 3import (
 4	"fmt"
 5	"time"
 6)
 7
 8func processRequest(id int) {
 9	fmt.Printf("✅ Processed request %d at %s\n", id, time.Now().Format("15:04:05"))
10}
11
12func main() {
13	const maxRequests = 10
14	const rateLimit = time.Second // یک درخواست در هر ثانیه
15
16	ticker := time.NewTicker(rateLimit)
17	defer ticker.Stop()
18
19	requests := make(chan int)
20
21	// Producer: ارسال درخواست‌ها با فاصله زمانی (مثلاً هر 300ms)
22	go func() {
23		for i := 1; i <= maxRequests; i++ {
24			requests <- i
25			time.Sleep(300 * time.Millisecond) // simulate incoming traffic
26		}
27		close(requests)
28	}()
29
30	// Consumer با Rate Limiting
31	for req := range requests {
32		<-ticker.C // اجازه پردازش فقط هر یک ثانیه یکبار
33		processRequest(req)
34	}
35}
 1$ go run main.go
 2✅ Processed request 1 at 23:00:01
 3✅ Processed request 2 at 23:00:02
 4✅ Processed request 3 at 23:00:03
 5✅ Processed request 4 at 23:00:04
 6✅ Processed request 5 at 23:00:05
 7✅ Processed request 6 at 23:00:06
 8✅ Processed request 7 at 23:00:07
 9✅ Processed request 8 at 23:00:08
10✅ Processed request 9 at 23:00:09
11✅ Processed request 10 at 23:00:10

در این مثال، پیاده‌سازی ساده‌ای از الگوی Rate Limiting در زبان Go با استفاده از time.Ticker نمایش داده شده است. هدف این کد آن است که اجازه دهد درخواست‌ها (در اینجا اعداد ۱ تا ۱۰) تنها با نرخ یک درخواست در هر ثانیه پردازش شوند. این کار برای جلوگیری از فشار زیاد بر روی سیستم یا رعایت محدودیت‌های خارجی بسیار رایج است.

ابتدا یک ticker با بازه‌ی زمانی یک ثانیه ساخته می‌شود. این ticker در هر ثانیه یک سیگنال روی کانال C خودش ارسال می‌کند. در همین حال، یک goroutine به عنوان تولیدکننده (Producer) تعریف شده که اعداد ۱ تا ۱۰ را بدون تأخیر وارد یک کانال requests می‌کند و سپس آن را می‌بندد. این کانال مانند یک صف عمل می‌کند.

در بخش مصرف‌کننده (Consumer)، که در main اجرا می‌شود، یک حلقه از روی requests می‌چرخد. قبل از پردازش هر درخواست، برنامه منتظر سیگنال از ticker.C می‌ماند. به این معنی که هر درخواست دقیقاً با فاصله‌ی زمانی یک ثانیه پردازش می‌شود. این مکانیزم باعث می‌شود اگر درخواست‌ها خیلی سریع وارد صف شوند، باز هم فقط با سرعت مجاز (در این مثال ۱ بر ثانیه) اجرا شوند.

در نهایت، تابع processRequest تنها مسئول چاپ شماره‌ی درخواست همراه با زمان اجرای آن است. این پیاده‌سازی اگرچه ساده است، اما پایه‌ای بسیار مناسب برای توسعه‌ی نسخه‌های پیشرفته‌تر مانند کنترل burst، توقف سریع با context، یا پیاده‌سازی توزیع‌شده فراهم می‌کند.

9.4.16.4 کاربردها #

  • Throttling API Calls: در زمانی که شما نیاز دارید درخواست‌های API را محدود کنید تا از محدودیت‌های تعیین‌شده توسط ارائه‌دهنده API تجاوز نکنید.
  • کنترل بار سیستم: برای جلوگیری از استفاده بیش از حد از منابع سیستم، مانند پردازنده یا پایگاه داده.
  • پردازش داده‌های ورودی: زمانی که داده‌های ورودی بسیار سریع‌تر از ظرفیت پردازش وارد می‌شوند، این الگو می‌تواند سرعت پردازش را مدیریت کند.
  • زمان‌بندی رویدادها: برای انجام عملیات در فواصل زمانی معین مانند ارسال ایمیل‌های گروهی.