مدیریت نرخ درخواست در ASP.NET Core: پیکربندیهایی که زیر بار ترافیکی شکست میخورند
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۶/۰۵ ۱۰:۳۵
آدرس: www.dntips.ir
System.Threading.RateLimiting ارائه داده است؛ بنابراین نیازی به نصب پکیجهای متفرقه نیست. کافی است یک Policy ساده ثبت کنید، عددی معقول برای آن در نظر بگیرید و آن را به Endpoint متصل کنید تا به درخواستهای اضافی پاسخ 429 Too Many Requests داده شود:builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("api", limiter =>
{
limiter.PermitLimit = 100;
limiter.Window = TimeSpan.FromSeconds(10);
limiter.QueueLimit = 0;
});
});
// ...
app.UseRateLimiter();
app.MapGet("/api/orders", GetOrders)
.RequireRateLimiting("api");QueueLimit است. نگهداشتن درخواستها در صف در ظاهر به هموارسازی بار ترافیکی کمک میکند، اما در عمل فشار را به تأخیر انباشته (Accumulated Latency) تبدیل میکند.QueueLimit = 0 است.| رویکرد فنی | هدف محافظت | نحوه پیادهسازی در ASP.NET Core |
| محدودسازی همزمانی (Concurrency Limiting) | کنترل حداکثر پردازشهای همزمان جهت محافظت از CPU و اتصالات دیتابیس | options.AddConcurrencyLimiter(...) |
| توزیع پنجرهای شناور یا سطل توکن | جلوگیری از جهشهای لحظهای در مرز بازههای زمانی | options.AddSlidingWindowLimiter(...) یا options.AddTokenBucketLimiter(...) |
| پارتیشنبندی محدودیتها (Partitioned Limiter) | ایزولهسازی سهمیه کلاینتها و جلوگیری از اثر همسایه پر سر و صدا (Noisy Neighbor) | PartitionedRateLimiter.Create |
| رد سریع بدون بافر (Fail-Fast Rejection) | جلوگیری از حبس حافظه و تجمع تاخیر در صف | QueueLimit = 0 به همراه RejectionStatusCode = 429 |
// نمونه پیکربندی کنترل همزمانی پارتیشنبندیشده بر اساس کاربر یا IP
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
// استخراج شناسه کاربر، کلید اختصاصی API یا IP کلاینت
var partitionKey = httpContext.User.Identity?.Name
?? httpContext.Connection.RemoteIpAddress?.ToString()
?? "anonymous";
return RateLimitPartition.GetConcurrencyLimiter(
partitionKey,
_ => new ConcurrencyLimiterOptions
{
PermitLimit = 20, // سقف دقیق درخواستهای همزمان به ازای هر کاربر
QueueLimit = 0 // شکست سریع و عدم تشکیل صف در حافظه
});
});
});429 Too Many Requests رد میشوند.options.AddFixedWindowLimiter("orders", limiter =>
{
limiter.PermitLimit = 1_000;
limiter.Window = TimeSpan.FromMinutes(1);
limiter.QueueLimit = 0;
});[ پنجره ۱ (دقیقه اول) ] [ پنجره ۲ (دقیقه دوم) ]
|-----------------------||||||||| | |||||||||-----------------------|
ثانیه ۵۸ تا ۶۰ ثانیه ۰ تا ۲
(۱۰۰۰ درخواست) (۱۰۰۰ درخواست)
>>> نتیجه در مرز پنجره: ۲۰۰۰ درخواست در ۴ ثانیه (۵۰۰ درخواست بر ثانیه واقعی!)var orders = await dbContext.Orders
.AsNoTracking()
.Where(x => x.CustomerId == customerId)
.OrderByDescending(x => x.CreatedAt)
.Take(100)
.ToListAsync(cancellationToken);Connection Timeout).System.Threading.RateLimiting قرار داده است:| الگوریتم | نحوه رفتار در مرز بازه زمانی | مورد کاربرد ایدهآل |
| Fixed Window | مستعد جهشهای ۲ برابری ترافیک در مرز پنجره | سهمیهبندیهای تجاری، مسدودسازی ساده ابزارهای Scraping |
| Sliding Window | تقسیم پنجره به بخشهای کوچکتر (Segments) برای هموارسازی مرزها | APIهایی که پایداری زمان پاسخدهی (Latency) در آنها حیاتی است |
| Token Bucket | اعطای توکن با نرخ ثابت در کنار تعیین ظرفیت انفجاری مشخص (Burst Capacity) | کنترل دقیق جریان داده و جلوگیری از ترافیک جهشی ناگهانی |
// جایگزینی Fixed Window با Sliding Window برای حذف جهش مرزی
options.AddSlidingWindowLimiter("orders-sliding", limiter =>
{
limiter.PermitLimit = 1_000;
limiter.Window = TimeSpan.FromMinutes(1);
limiter.SegmentsPerWindow = 6; // تقسیم هر پنجره به ۶ سگمنت ۱۰ ثانیهای
limiter.QueueLimit = 0;
});options.AddTokenBucketLimiter("orders-token", limiter =>
{
limiter.TokenLimit = 50; // حداکثر ظرفیت انفجاری مجاز در یک لحظه
limiter.TokensPerPeriod = 16; // اضافه شدن ۱۶ توکن در هر ثانیه
limiter.ReplenishmentPeriod = TimeSpan.FromSeconds(1);
limiter.QueueLimit = 0;
});Scoped (مانند DbContext و ساختارهای ردیابی Entity Framework)، اشیای Activity مربوط به OpenTelemetry، بافرهای پاسخ و CancellationTokenها برای مدت طولانیتری زنده میمانند و تخصیصهای Gen 1 و Gen 2 را به حداکثر میرسانند.| ویژگی | الگوریتمهای مبتنی بر زمان (Time-Window) | کنترلکننده همزمانی (Concurrency Limiter) |
| محور کنترل | زمان و سهمیه ورودی (مثلاً ۱۰۰۰ در دقیقه) | تعداد پردازشهای موازی زنده (مثلاً ۲۰ اجرای همزمان) |
| محافظت در برابر | مصرف بیش از حد سهمیه تجاری، باتها و اسکرپرها | افت عملکرد دیتابیس، ThreadPool Starvation و کمبود حافظه |
| پاسخ به کندی سیستم | بیتفاوت به زمان اجرای درخواستها | مسدودسازی فوری ورود ترافیک جدید در صورت انباشت درخواستهای قبلی |
CreateChained در PartitionedRateLimiter هر دو لایه را همزمان اعمال کرد:builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
// تعریف یک محدودکننده زنجیرهای: کنترل فرکانس ورود + سقف پردازش همزمان
options.GlobalLimiter = PartitionedRateLimiter.CreateChained(
// لایه ۱: هموارسازی ورود درخواستها به ازای کاربر/IP
PartitionedRateLimiter.Create<HttpContext, string>(context =>
{
var key = context.User.Identity?.Name
?? context.Connection.RemoteIpAddress?.ToString()
?? "anonymous";
return RateLimitPartition.GetSlidingWindowLimiter(key, _ => new SlidingWindowLimiterOptions
{
PermitLimit = 120,
Window = TimeSpan.FromMinutes(1),
SegmentsPerWindow = 6,
QueueLimit = 0
});
}),
// لایه ۲: سقف صلب همزمانی کل برنامه جهت ایمنسازی دیتابیس و تردپول
PartitionedRateLimiter.Create<HttpContext, string>(_ =>
{
return RateLimitPartition.GetConcurrencyLimiter("global-concurrency", _ => new ConcurrencyLimiterOptions
{
PermitLimit = 50, // حداکثر ۵۰ درخواست باز در کل سیستم
QueueLimit = 0
});
})
);
});429 Too Many Requests پس زده میشوند؛ اما با وجود صف، به کلاینتها این شانس داده میشود که چند لحظه منتظر بمانند تا ظرفیت خالی شود و درخواستشان با موفقیت انجام پذیرد.options.AddConcurrencyLimiter("checkout", limiter =>
{
limiter.PermitLimit = 100;
limiter.QueueLimit = 1_000;
limiter.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
});QueueLimit) ظرفیت از دست رفته پایگاه داده را جبران نمیکند؛ صرفاً صف طویلی از درخواستهای معطل در پشت درهای بسته ایجاد میکند.429، ایجادکننده فشار معکوسِ صریح (Explicit Backpressure) است. این سیگنال فوری به کلاینتها و کلاسترهای بالادست اعلام میکند که سیستم در سقف ظرفیت است تا آنها سازوکارهای عقبنشینی تدریجی (Exponential Backoff) یا نمایش پیام مناسب به کاربر را فعال کنند.| رویکرد | رفتار در اوج ترافیک | پیامد برای دیتابیس و حافظه | رفتار در زمان بازیابی |
صف طولانی (QueueLimit > 0) | تبدیل خطا به تأخیر بالا و انباشت کارهای معلق | اشغال طولانی حافظه، خطر لغو کلاینت و تکرار درخواست | ادامه کندی شدید سیستم حتی پس از پایان جهش ترافیک |
رد فوری (QueueLimit = 0) | شکست سریع (Fail-Fast) و ارسال بلادرنگ خطای ۴۲۹ | ایزوله ماندن دیتابیس در سقف ایمن و آزادسازی فوری منابع | بازیابی بلادرنگ سیستم به محض فروکش کردن بار ورودی |
AcquireAsync یا استفاده از میانافزار، حتماً اتصال به HttpContext.RequestAborted را بررسی کرده و ترجیحاً QueueLimit را روی حداقل ممکن (صفر یا مقادیر تکرقمی بسیار محدود) تنظیم کنید:// استفاده از صف بسیار کوچک همراه با Fail-Fast برای حفظ بودجه تاخیر
options.AddConcurrencyLimiter("checkout-defensive", limiter =>
{
limiter.PermitLimit = 50; // سقف سختگیرانه همزمانی روی دیتابیس
limiter.QueueLimit = 5; // صف بسیار محدود صرفاً برای هموارسازی میلیثانیهای
limiter.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
});
// بازگرداندن پاسخ شفاف با هدر Retry-After
options.OnRejected = async (context, token) =>
{
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
context.HttpContext.Response.Headers.RetryAfter = "5"; // پیشنهاد عقبنشینی ۵ ثانیهای به کلاینت
await context.HttpContext.Response.WriteAsync("سرویس موقتاً در حداکثر ظرفیت است. لطفاً چند لحظه بعد مجدداً تلاش کنید.", token);
};options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
{
var key = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
return RateLimitPartition.GetFixedWindowLimiter(
partitionKey: key,
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1),
QueueLimit = 0
});
});context.Connection.RemoteIpAddress در سرور Kestrel، آدرس IP همان لایه میانی (پروکسی یا لودبالانسر) خواهد بود.429 Too Many Requests مسدود خواهند شد.ForwardedHeadersOptions در ابتدای پایپلاین به شکل امن رجیستر شود تا هدرهای X-Forwarded-For و X-Forwarded-Proto بهدرستی مقداردهی شوند:builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
// افزودن شبکهها یا لودبالانسرهای قابل اعتماد برای جلوگیری از جعل هدر
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
// ...
// این میانافزار حتماً باید قبل از UseRateLimiter فراخوانی شود
app.UseForwardedHeaders();
app.UseRateLimiter();X-Forwarded-For به عنوان کلید پارتیشن استفاده کنید، مهاجم میتواند با ارسال مقادیر تصادفی در هر درخواست، کلید پارتیشن جدیدی بسازد و سیستم Rate Limiting را بهسادگی دور بزند.| نوع سیستم / اندپوینت | هویت اصلی درخواستدهنده | کلید پارتیشن ایدهآل | علت انتخاب |
| اندپوینت عمومی / احرازشده نشده | کلاینت شبکه | آدرس IP امن (از طریق Forwarded Headers) | تنها داده در دسترس برای مسدودسازی باتها و اسکرپرها |
| سرویسهای تجاری و B2B | مشتری / کلاینت مجاز | X-Api-Key یا شناسه اشتراک | رعایت سهمیه قرارداد تجاری بدون تأثیر گرفتن از موقعیت مکانی |
| سیستمهای چندمستأجری (Multi-Tenant) | کل سازمان / Tenant | TenantId (از Claim یا Header) | جلوگیری از اشباع دیتابیس مشترک توسط یک سازمان با کاربران متعدد |
| سامانههای تعاملی سازمانی | کاربر نهایی | ClaimTypes.NameIdentifier | تضمین عدالت مصرف میان اعضای مختلف یک سیستم |
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
{
// ۱. بررسی شناسه سازمانی برای سیستمهای چندمستأجری
var tenantId = context.User.FindFirst("tenant_id")?.Value;
if (!string.IsNullOrEmpty(tenantId))
{
return RateLimitPartition.GetSlidingWindowLimiter($"tenant_{tenantId}", _ => new SlidingWindowLimiterOptions
{
PermitLimit = 500,
Window = TimeSpan.FromMinutes(1),
SegmentsPerWindow = 6,
QueueLimit = 0
});
}
// ۲. بررسی شناسه کاربر احرازهویتشده
var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (!string.IsNullOrEmpty(userId))
{
return RateLimitPartition.GetSlidingWindowLimiter($"user_{userId}", _ => new SlidingWindowLimiterOptions
{
PermitLimit = 120,
Window = TimeSpan.FromMinutes(1),
SegmentsPerWindow = 4,
QueueLimit = 0
});
}
// ۳. استفاده از IP امن برای کاربران ناشناس
var ipAddress = context.Connection.RemoteIpAddress?.ToString() ?? "unknown_ip";
return RateLimitPartition.GetFixedWindowLimiter($"ip_{ipAddress}", _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 30,
Window = TimeSpan.FromMinutes(1),
QueueLimit = 0
});
});GET /api/products/{id}
POST /api/reports/export
POST /api/orders
GET /api/healthproducts یک مدل کوچک و کششده در Redis را با کمترین بار پردازشی برمیگرداند.reports/export یک کوئری تحلیلی سنگین را روی دیتابیس اجرا کرده، دادهها را مرتبسازی و به صورت یک فایل حجیم استریم میکند.orders تراکنشهای مالی، رزرو موجودی و قفلگذاری روی جداول پایگاه داده را انجام میدهد.health صرفاً وضعیت زنده بودن پروسس را بررسی میکند و تقریباً هیچ منبعی مصرف نمیکند.GET products) که سرور بهراحتی توانایی پاسخ به هزاران مورد از آنها را دارد، بیدلیل با خطای 429 Too Many Requests رد میشوند.health پشت همان سهمیه سراسری قرار گیرد، پر شدن سهمیه منجر به رد درخواستهای Health Probe توسط لودبالانسر یا Kubernetes شده و پروسس سالم به اشتباه Restart میشود!AddPolicy سیاستهای نامگذاریشده بسازید و با متد RequireRateLimiting آنها را به اندپوینتهای متناظر متصل کنید:| دسته بار کاری | نمونه اندپوینت | راهبرد کنترل ظرفیت | الگوریتم مناسب |
| خواندنهای سبک و کششده | /api/products/{id} | سهمیه دستودلبازانه همراه با قابلیت Burst | Token Bucket یا Sliding Window |
| عملیات تحلیلی و سنگین | /api/reports/export | کنترل صلب همزمانی (Concurrency) بدون صف | Concurrency Limiter (QueueLimit = 0) |
| تراکنشهای حساس و نوشتنی | /api/orders | ترکیب سهمیه کاربر با سقف همزمانی | Partitioned Rate Limiting |
| بررسی سلامت و متادیتا | /api/health | معاف از محدودیت | DisableRateLimiting |
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
// ۱. سیاست درخواستهای سبک و خواندنی: سهمیه بالا با مدیریت جهش
options.AddTokenBucketLimiter("read-policy", limiter =>
{
limiter.TokenLimit = 200;
limiter.TokensPerPeriod = 50;
limiter.ReplenishmentPeriod = TimeSpan.FromSeconds(1);
limiter.QueueLimit = 0;
});
// ۲. سیاست گزارشهای سنگین: سقف سختگیرانه همزمانی روی منابع دیتابیس
options.AddConcurrencyLimiter("expensive-report-policy", limiter =>
{
limiter.PermitLimit = 5; // حداکثر ۵ گزارش همزمان در کل پروسس
limiter.QueueLimit = 0; // رد فوری درخواستهای اضافه جهت حفظ دیتابیس
});
// ۳. سیاست تراکنشهای ایجاد سفارش: تفکیک بر اساس شناسه کاربر
options.AddPolicy("transactional-write-policy", context =>
{
var userId = context.User.Identity?.Name
?? context.Connection.RemoteIpAddress?.ToString()
?? "anonymous";
return RateLimitPartition.GetSlidingWindowLimiter(userId, _ => new SlidingWindowLimiterOptions
{
PermitLimit = 10,
Window = TimeSpan.FromMinutes(1),
SegmentsPerWindow = 2,
QueueLimit = 0
});
});
});
// ...
app.UseRateLimiter();
// اتصال صریح سیاستها به اندپوینتها
app.MapGet("/api/products/{id}", GetProduct)
.RequireRateLimiting("read-policy");
app.MapPost("/api/reports/export", ExportReport)
.RequireRateLimiting("expensive-report-policy");
app.MapPost("/api/orders", CreateOrder)
.RequireRateLimiting("transactional-write-policy");
// معافسازی اندپوینتهای زیرساختی
app.MapGet("/api/health", () => Results.Ok(new { status = "Healthy" }))
.DisableRateLimiting();TokenLimit) بزرگتر از توان تابآوری واقعی منبعی است که قصد محافظت از آن را دارید.options.AddTokenBucketLimiter("dashboard-burst", limiter =>
{
limiter.TokenLimit = 100; // سقف ظرفیت انفجاری
limiter.TokensPerPeriod = 10; // تولید ۱۰ توکن جدید
limiter.ReplenishmentPeriod = TimeSpan.FromSeconds(1); // در هر ثانیه
limiter.QueueLimit = 0;
});PartitionedRateLimiter.CreateChained است:builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.GlobalLimiter = PartitionedRateLimiter.CreateChained(
// لایه ۱: اعمال سهمیه تجاری و مجاز شمردن جهشهای کوتاهمدت کاربر
PartitionedRateLimiter.Create<HttpContext, string>(context =>
{
var userId = context.User.Identity?.Name
?? context.Connection.RemoteIpAddress?.ToString()
?? "anonymous";
return RateLimitPartition.GetTokenBucketLimiter(userId, _ => new TokenBucketRateLimiterOptions
{
TokenLimit = 60, // حداکثر جهش مجاز به ازای کاربر
TokensPerPeriod = 10,
ReplenishmentPeriod = TimeSpan.FromSeconds(1),
QueueLimit = 0
});
}),
// لایه ۲: سقف فیزیکی همزمانی روی دیتابیس و منابع سرور
PartitionedRateLimiter.Create<HttpContext, string>(_ =>
{
return RateLimitPartition.GetConcurrencyLimiter("db-capacity-guard", _ => new ConcurrencyLimiterOptions
{
PermitLimit = 30, // حداکثر ۳۰ اجرای واقعاً همزمان روی منابع مشترک
QueueLimit = 0
});
})
);
});| مفهوم | ماهیت | متولی در معماری | ابزار در ASP.NET Core |
| امکان جهش (Burst Allowance) | یک ویژگی محصول و بیزینس | لایه کاربر / سهمیه | TokenBucketLimiter |
| تحمل جهش (Burst Tolerance) | یک ویژگی سختافزاری و زیرساختی | لایه محافظت از منبع | ConcurrencyLimiter |
System.Threading.RateLimiting) را به کلی تغییر میدهد؛ زیرا این ابزار بهطور پیشفرض درونحافظهای (In-Memory) است و وضعیت آن درون همان تک پروسس مدیریت میشود.options.AddFixedWindowLimiter("client-quota", limiter =>
{
limiter.PermitLimit = 100;
limiter.Window = TimeSpan.FromMinutes(1);
limiter.QueueLimit = 0;
});[ کلاینت / مهاجم ]
│
▼ (ارسال درخواستها با الگوی Round-Robin)
[ Load Balancer ]
├───► نود ۱ (مجاز: ۱۰۰) ──► مصرف: ۱۰۰
├───► نود ۲ (مجاز: ۱۰۰) ──► مصرف: ۱۰۰
│ ...
└───► نود ۱۰ (مجاز: ۱۰۰) ──► مصرف: ۱۰۰
────────────────────────────────────────────────────
>>> مجموع ترافیک مجاز واردشده به دیتابیس مشترک: ۱,۰۰۰ درخواست در دقیقه!| سطح اعمال | هدف اصلی | ابزار و لایه پیادهسازی مناسب |
| سراسری (Distributed) | سهمیه تجاری، مهار باتها، صورتحساب | لایه API Gateway (نظیر YARP/Nginx/Kong) یا Redis متمرکز |
| محلی (In-Process) | حفظ ThreadPool، سقف کانکشنهای دیتابیس | میانافزار داخلی ASP.NET Core (ConcurrencyLimiter) |
ConcurrencyLimiter ساده و بدون نیاز به شبکه، سقف منابع فیزیکی همان پروسس را گارانتی میکند:// نمونه پیکربندی درون پروسس برای محافظت مستقل هر نود
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
// کنترل سختگیرانه همزمانی روی همان نمونه محلی بدون وابستگی به شبکه
options.AddConcurrencyLimiter("local-capacity-guard", limiter =>
{
limiter.PermitLimit = 40; // حداکثر ۴۰ عملیات همزمان روی این نود
limiter.QueueLimit = 0; // رد آنی بدون ایجاد تأخیر در حافظه
});
});429 بازگردانده شده است. این آزمایش تنها یک موضوع را اثبات میکند: میانافزار در پایپلاین رجیستر شده است. اما هرگز نشان نمیدهد که سرویس شما در شرایط اشباع واقعی زنده خواهد ماند یا خیر.| استراتژی محدودکننده | الگوی اصلی حمله در تست بار | خطای مورد انتظار برای سنجش و پیشگیری |
| Fixed Window | ترافیک انفجاری در مرز (Boundary Bursting): ارسال ترافیک با حجم دو برابر سقف مجاز در فاصله چند میلیثانیه قبل و بعد از ریست شدن پنجره زمانی. | جلوگیری از ورود ناگهانی موج دوبرابری ترافیک و اشباع اتصالات دیتابیس در مرز پنجرهها. |
| Sliding Window | اشباع سگمنتها: شلیک پالسهای مقطعی ترافیک در بازههای کوچک خردشده (Segments). | اطمینان از عملکرد صحیح میانگین متحرک و عدم مصرف بیرویه حافظه برای رهگیری وضعیت سگمنتها. |
| Token Bucket | تخلیه آنی سطل + فشار مستمر: خالی کردن یکباره تمام توکنهای انباشته و سپس ادامه بار سنگین بالاتر از نرخ پر شدن مجدد (Refill Rate). | اثبات ثبات زمانبندی شارژ توکنها و عدم وقوع ناهماهنگی تحت قفلهای داخلی تردها. |
| Concurrency Limiter | تزریق تاخیر در وابستگیها (Latency Injection): ایجاد تاخیر مصنوعی ۲ تا ۵ ثانیهای در کوئریهای پایگاه داده یا وبسرویسهای خارجی. | حصول اطمینان از اینکه تعداد پردازشهای زنده همزمان دقیقاً در سقف تعیینشده مهار میشود و تردپول آسیب نمیبیند. |
| پارتیشنبندی چندمستأجری | شبیهسازی همسایه پر سر و صدا (Noisy Neighbor): اشباع کامل سهمیه سازمان A و همزمان ارسال ترافیک استاندارد روی سازمانهای B و C. | اطمینان از اینکه اشباع یک پارتیشن، عملکرد و سرعت پاسخدهی به سایر کاربران معتبر را مختل نمیکند. |
QueueLimit > 0) اغلب پاشنه آشیل سیستمهای تحت فشار است:Scoped را در حافظه قفل نگه میدارد. در صورت تداوم فشار، صفهای طولانی باعث رفتن آبجکتها به Gen 2 و در نهایت فریز سیستم بر اثر Garbage collection یا خطای OutOfMemoryException میشوند.Microsoft.AspNetCore.RateLimiting):rate-limiting.request.lease.duration).rate-limiting.request.time-in-queue).ThreadPool.PendingWorkItemCount) و مدتزمان رقابت بر سر قفلها (Lock Contention).System.Threading.RateLimiting ابزارهایی معتبر و استاندارد هستند، اما هر کدام اگر در جای نادرست به کار گرفته شوند، منشأ شکست خواهند بود:| منبع تحت خطر / سناریوی بحران | بُعد اصلی محدودسازی | الگوی پیشنهادی در ASP.NET Core |
| اتصالات پایگاه داده (Connection Pool) / حافظه | حداکثر اجرای همزمان (Concurrency) | ConcurrencyLimiter با QueueLimit = 0 |
| حملات بات، اسکرپینگ و اندپوینتهای عمومی | نرخ ورود به ازای هویت شبکه | PartitionedRateLimiter روی IP امن با SlidingWindow |
| سهمیهبندی پلنهای تجاری و مشتریان سازمانی | حجم کل در بازه زمانی مشخص | TokenBucket با کلید پارتیشن TenantId یا API-Key |
| عملیات سنگین I/O و گزارشگیری تحلیلی | ایزولهسازی سهمیه پردازش پرهزینه | سیاست اختصاصی اندپوینت با RequireRateLimiting مجزا |
| سرویسهای توزیعشده با Autoscaling | تفکیک لایههای محلی از سراسری | سهمیه تجاری در API Gateway + کنترل همزمانی در کانتینر |
429، مکانیزمی حیاتی برای ایجاد فشار معکوس (Backpressure) است.TenantId برای دیتابیس مشترک سازمانی).PermitLimit نیست؛ بلکه میزان ظرفیت پردازش مفیدی است که سرویس شما میتواند در اوج بحران ترافیک بدون افت کیفیت تحویل دهد. یک سیاست هوشمند، درخواستهای مازاد را در اولین نقطه ممکن متوقف میکند تا سرویسدهی سالم به سایر کاربران حفظ شود؛ در حالی که یک سیاست اشتباه، جهشهای مخرب را وارد سیستم کرده یا درخواستها را پشت صفهای طولانی پنهان میسازد تا در نهایت داشبوردها وضعیتی آرام را نشان دهند در حالی که اپلیکیشن در حال فروپاشی کامل است.