9.4.5 الگو Drop

9.4.5 الگو Drop

9.4.5.1 توضیحات #

الگوی Drop یا Drop Overflow یکی از الگوهای حیاتی برای سیستم‌هایی است که ممکن است با موجی از درخواست‌ها روبرو شوند که بیش از ظرفیت واقعی سیستم است. در این الگو، زمانی که صف یا ظرفیت پردازش درخواست‌ها (مثلاً یک کانال یا بافر) پر می‌شود، به جای اینکه سیستم را دچار ازدحام، توقف یا crash کند، به سادگی درخواست‌های اضافی (یا جدیدتر یا قدیمی‌تر، بر اساس سیاست) را حذف (Drop) می‌کند. این کار باعث می‌شود سرویس همواره پایدار و قابل اطمینان باقی بماند و منابع اصلی به خاطر یک سناریوی غیرعادی یا حمله دچار مشکل نشود.

کاربرد این الگو به‌خصوص در سرویس‌های زیرساختی حیاتی مانند DNS، load balancer، یا حتی سیستم‌های realtime که نمی‌خواهند به هیچ قیمتی دچار backlog و تاخیر بالا شوند، بسیار رایج است. به عنوان مثال، در یک سرور DNS وقتی تعداد درخواست‌ها از ظرفیت کانال یا worker pool بیشتر شود، درخواست‌های جدید بلافاصله drop می‌شوند تا latency پایین بماند و سرور همچنان responsive بماند. این الگو به عنوان بخشی از استراتژی کلی Backpressure نیز به کار می‌رود و برای معماری‌هایی که تحمل “از دست رفتن بعضی درخواست‌ها” بهتر از “کُند شدن یا قطع کامل سرویس” است، ایده‌آل محسوب می‌شود.

در پیاده‌سازی‌های Go معمولاً یک کانال بافر دار تعریف می‌شود و اگر هنگام ارسال داده به کانال، با پر بودن مواجه شویم (مثلاً با select غیر بلوک‌کننده)، درخواست به راحتی Drop می‌شود و کنترل به سرعت به کد اصلی بازمی‌گردد. این تکنیک ساده، موثر و بسیار idiomatic در پروژه‌های تولیدی Go است و به حفظ کیفیت خدمات و جلوگیری از overload شدن سیستم کمک شایانی می‌کند.

9.4.5.2 دیاگرام #

flowchart LR A[درخواست‌های ورودی] -->|درخواست 1| Q((Channel Queue)) A -->|درخواست 2| Q A -->|درخواست 3| Q A -->|درخواست ...| Q Q -- "در صورت پر بودن" --> D[Drop Request
رد شدن درخواست] Q -- "در صورت ظرفیت داشتن" --> W[Worker] W --> R[نتیجه/پردازش] style Q fill:#ffe9c6,stroke:#efb64f,stroke-width:2px style D fill:#ffeaea,stroke:#ff5b5b,stroke-width:2px style W fill:#e3ffe3,stroke:#6bc76b,stroke-width:2px style R fill:#e0eaff,stroke:#476ebd,stroke-width:2px

9.4.5.3 نمونه کد #

 1package main
 2
 3import (
 4	"fmt"
 5	"sync"
 6	"time"
 7)
 8
 9func main() {
10	const bufferSize = 3
11	const numData = 10
12
13	in := make(chan int, bufferSize)
14	out := make(chan int, bufferSize)
15	var wg sync.WaitGroup
16
17	// تولیدکننده: تولید داده با Drop در صورت پر بودن کانال
18	wg.Add(1)
19	go func() {
20		defer wg.Done()
21		for i := 1; i <= numData; i++ {
22			select {
23			case in <- i:
24				fmt.Printf("[Producer] Sent: %d\n", i)
25			default:
26				fmt.Printf("[Producer] Drop: %d (buffer full)\n", i)
27			}
28			time.Sleep(100 * time.Millisecond)
29		}
30		close(in)
31	}()
32
33	// مصرف‌کننده: مصرف داده با Drop اگر مصرف‌کننده بعدی نتواند داده را بپذیرد
34	wg.Add(1)
35	go func() {
36		defer wg.Done()
37		for data := range in {
38			select {
39			case out <- data:
40				fmt.Printf("[Consumer] Forwarded: %d\n", data)
41			default:
42				fmt.Printf("[Consumer] Drop: %d (output buffer full)\n", data)
43			}
44			time.Sleep(150 * time.Millisecond)
45		}
46		close(out)
47	}()
48
49	// جمع‌آوری خروجی
50	for result := range out {
51		fmt.Printf("[Result] Received: %d\n", result)
52	}
53
54	wg.Wait()
55}
 1$ go run main.go
 2[Producer] Sent: 1
 3[Result] Received: 1
 4[Consumer] Forwarded: 1
 5[Producer] Sent: 2
 6[Consumer] Forwarded: 2
 7[Result] Received: 2
 8[Producer] Sent: 3
 9[Producer] Sent: 4
10[Consumer] Forwarded: 3
11[Result] Received: 3
12[Producer] Sent: 5
13[Consumer] Forwarded: 4
14[Result] Received: 4
15[Producer] Sent: 6
16[Producer] Sent: 7
17[Consumer] Forwarded: 5
18[Result] Received: 5
19[Producer] Sent: 8
20[Consumer] Forwarded: 6
21[Result] Received: 6
22[Producer] Sent: 9
23[Producer] Drop: 10 (buffer full)
24[Consumer] Forwarded: 7
25[Result] Received: 7
26[Consumer] Forwarded: 8
27[Result] Received: 8
28[Consumer] Forwarded: 9
29[Result] Received: 9

در این مثال از الگوی Drop Overflow، یک سناریوی واقعی‌تر برای مدیریت بار بیش از ظرفیت در سیستم‌های concurrent نمایش داده شده است. ابتدا دو کانال بافر‌دار (یکی برای ورودی و دیگری برای خروجی) تعریف شده‌اند تا شبیه‌ساز صف‌هایی با ظرفیت محدود باشند. یک goroutine به عنوان تولیدکننده، داده‌هایی با مقادیر مختلف (از ۱ تا ۱۰) تولید می‌کند و در تلاش است آن‌ها را وارد کانال ورودی کند. اگر ظرفیت کانال ورودی پر باشد، داده جدید بدون معطلی Drop شده و پیام مناسبی در لاگ چاپ می‌شود؛ این رفتار باعث می‌شود که goroutine تولیدکننده هیچگاه بلاک نشود و سیستم دچار اختلال یا توقف نگردد.

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

در انتهای برنامه، خروجی‌ها از کانال خروجی جمع‌آوری و چاپ می‌شوند تا وضعیت مصرف موفق داده‌ها قابل مشاهده باشد. با کمک sync.WaitGroup اطمینان حاصل شده که تمام goroutineها قبل از پایان برنامه به درستی خاتمه پیدا می‌کنند و هیچ goroutine سرگردان یا leak اتفاق نمی‌افتد. با افزودن تأخیرهای زمانی متفاوت در تولید و مصرف داده‌ها، سناریوهای متفاوتی از فشار و ترافیک شبیه‌سازی شده تا نقاط Drop مختلف به خوبی دیده شوند. این مثال نشان‌دهنده کاربرد عملی و idiomatic این الگو در Go است؛ جایی که Drop شدن بخشی از داده‌ها به جای کندی یا Crash سیستم، انتخابی هوشمندانه و تولیدی محسوب می‌شود، به ویژه در سیستم‌های real-time، سرویس‌های زیرساختی یا سناریوهایی با بار متغیر.

9.4.5.4 کاربردها #

  • محدودسازی نرخ پردازش (Rate Limiting): زمانی که با حجم بالایی از داده‌های ورودی یا درخواست‌ها سروکار دارید، این الگو به شما کمک می‌کند تا تنها تعداد مشخصی داده را در هر لحظه بپذیرید و باقی داده‌های مازاد را Drop کنید؛ به این ترتیب، نرخ پردازش سیستم ثابت و قابل کنترل باقی می‌ماند و از overload یا کندی سیستم جلوگیری می‌شود.
  • ثبت لاگ و مانیتورینگ (Logging/Monitoring): در سیستم‌های لاگ‌گیری یا مانیتورینگ با حجم بالای رویداد، ممکن است توان نوشتن روی دیسک یا ارسال داده به سرور مرکزی محدود باشد. با پیاده‌سازی Drop Pattern می‌توانید پیام‌های اضافی یا کم‌اهمیت را به سادگی حذف کنید تا ضمن حفظ پایداری سرویس، داده‌های کلیدی با کمترین تأخیر ثبت شوند.
  • مدیریت صف و کنترل ازدحام (Queue Management & Congestion Control): هنگام استفاده از صف‌هایی که توسط کانال‌های بافر‌دار مدیریت می‌شوند، به جای مسدود شدن تولیدکننده یا ایجاد backlog زیاد، می‌توانید در صورت پر شدن صف، داده‌های جدید را Drop کنید. این رفتار به ویژه در سناریوهای با بار متغیر، باعث افزایش پایداری سیستم و کاهش سربار مدیریت صف می‌شود.
  • کنترل ترافیک شبکه و ورودی (Traffic Shaping & Throttling): در سرورها یا سرویس‌هایی که با حجم بالای ترافیک یا درخواست همزمان مواجه می‌شوند (مانند API Gateway یا Load Balancer)، Drop Pattern کمک می‌کند که بخشی از ترافیک ورودی در صورت نبود ظرفیت حذف شود و کیفیت خدمات به کاربران باقی‌مانده حفظ شود. این رویکرد، راهکاری مؤثر برای جلوگیری از حملات DoS یا اسپایک‌های ناگهانی است.
  • سیستم‌های بلادرنگ و حساس به تأخیر (Real-time & Low Latency Systems): در سیستم‌های real-time مانند سیستم‌های پردازش صدا، تصویر، یا تحلیل لحظه‌ای داده‌های حسگرها، معمولاً داده‌ها باید در بازه زمانی مشخص پردازش شوند. در صورت پر بودن بافر یا عدم توان مصرف داده، داده‌های جدید Drop می‌شوند تا latency پایین و پاسخ‌دهی سریع سیستم حفظ شود.
  • سرویس‌های زیرساختی حیاتی (مانند DNS و پیام‌رسان‌ها): در زیرساخت‌هایی مثل DNS، صف‌های کاری باید کوتاه و پاسخ‌دهی سریع باشد. اگر صف پر شود، درخواست‌های جدید به سرعت Drop می‌شوند تا سرویس‌دهی پایدار باقی بماند و سیستم دچار اشباع یا توقف نشود.