İLERİ
Rate Limiting
.NET 7+ AddRateLimiter() middleware'i ile built-in rate limiting. Redis backing store ile multi-instance'da paylaşımlı limit.
Kod örneği görünümü
Bu sayfadaki eşleşen örnekleri seçilen istemciye göre gösterir.
Ne Zaman Rate Limiting Kullan / Kullanma
| Kullan | Kullanma | Gerçek Hayat |
|---|---|---|
| Public API (abuse koruması) | Internal service-to-service (trust boundary içi) | SaaS API: Free plan 100 req/dk, Pro plan 1000 req/dk |
| Login/register endpoint (brute-force) | Batch job / admin endpoint (kendi sisteminiz) | Auth: 5 başarısız login → 15dk ban (credential stuffing engeli) |
| Expensive endpoint (rapor, export) | Zaten queue ile throttle edilen işlemler | Export: CSV export max 2/saat (her biri 30s CPU) |
| Webhook receiver (upstream spike) | Read-only cached endpoint (zaten ucuz) | Payment gateway: Webhook flood'unda kendi DB'ni koru |
Endpoint tipine göre algoritma seçimi:
- Login/Register: Fixed Window (basit, 5 attempt/15dk yeterli)
- Public REST API: Sliding Window (adil, burst yok, kullanıcı-facing)
- Mobile push notification: Token Bucket (batch gönderim spike'larına tolerans)
- Payment gateway callback: Leaky Bucket (downstream DB'ye sabit yük)
- WebSocket message: Token Bucket (chat'te hızlı mesaj burst'ü normal)
Algoritma Seçimi
| Algoritma | Nasıl Çalışır | Avantaj | Dezavantaj | Ne Zaman |
|---|---|---|---|---|
| Fixed Window | Sabit zaman diliminde sayaç (ör: 0:00-1:00) | Basit, O(1) memory | Pencere sınırında burst (2× limit) | Internal API, basit koruma |
| Sliding Window | Sorted Set + zaman bazlı kayan pencere | Burst yok, adil dağılım | O(N) memory (her request bir entry) | Public API, kullanıcı-facing |
| Token Bucket | Sabit hızla token eklenir, request token tüketir | Burst'a izin (bucket kapasitesi kadar) | Biraz daha karmaşık | Spike-tolerant API'lar |
| Leaky Bucket | Queue + sabit çıkış hızı | Tamamen düz throughput | Burst tolere etmez, latency ekler | Downstream koruma, payment |
Önerilen: Çoğu API için sliding window yeterli. Burst istiyorsan token bucket. Downstream'i koruyorsan leaky bucket.
Sliding Window (Production-ready)
# Sorted Set sliding window
ZREMRANGEBYSCORE rate:user:1001 0 <now-60000>
ZCARD rate:user:1001
# < limit ise:
ZADD rate:user:1001 <now> "<now>:<random>"
PEXPIRE rate:user:1001 60000
public class RateLimiterService
{
private readonly IDatabase _redis;
private static readonly LuaScript _slidingWindowScript = LuaScript.Prepare(@"
local key = @key
local limit = tonumber(@limit)
local window = tonumber(@window)
local now = tonumber(@now)
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. ':' .. math.random(1000000))
redis.call('PEXPIRE', key, window)
return count + 1
end
return -1
");
public RateLimiterService(IConnectionMultiplexer mux)
=> _redis = mux.GetDatabase();
public async Task<RateLimitResult> CheckAsync(string clientId, int limit, TimeSpan window)
{
var result = (int)await _redis.ScriptEvaluateAsync(_slidingWindowScript, new
{
key = (RedisKey)$"rate:{clientId}",
limit,
window = (long)window.TotalMilliseconds,
now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
});
return new RateLimitResult
{
IsAllowed = result > 0,
CurrentCount = result > 0 ? result : limit,
Limit = limit
};
}
}
// Middleware
public class RateLimitMiddleware
{
private readonly RequestDelegate _next;
private readonly RateLimiterService _rateLimiter;
public RateLimitMiddleware(RequestDelegate next, RateLimiterService rateLimiter)
{
_next = next;
_rateLimiter = rateLimiter;
}
public async Task InvokeAsync(HttpContext context)
{
var clientId = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
var result = await _rateLimiter.CheckAsync(clientId, limit: 100, TimeSpan.FromMinutes(1));
context.Response.Headers["X-RateLimit-Limit"] = "100";
context.Response.Headers["X-RateLimit-Remaining"] =
Math.Max(0, 100 - result.CurrentCount).ToString();
if (!result.IsAllowed)
{
context.Response.StatusCode = 429;
await context.Response.WriteAsync("Rate limit exceeded");
return;
}
await _next(context);
}
}
ASP.NET Built-in Rate Limiter + Redis
// NuGet: dotnet add package System.Threading.RateLimiting
builder.Services.AddRateLimiter(options =>
{
// Global sliding window — Redis-backed (custom partitioner)
options.AddPolicy("api", httpContext =>
{
var clientIp = httpContext.Connection.RemoteIpAddress?.ToString() ?? "anon";
return RateLimitPartition.GetSlidingWindowLimiter(clientIp, _ =>
new SlidingWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1),
SegmentsPerWindow = 6, // 10 saniyelik segment'ler
QueueLimit = 0
});
});
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.OnRejected = async (context, ct) =>
{
context.HttpContext.Response.Headers["Retry-After"] = "60";
await context.HttpContext.Response.WriteAsync(
"Rate limit exceeded. Try again later.", ct);
};
});
app.UseRateLimiter();
// Endpoint'e uygula
app.MapGet("/api/data", () => Results.Ok())
.RequireRateLimiting("api");
Built-in vs Custom Lua: Built-in rate limiter tek instance'da memory-based çalışır — multi-instance'da her pod kendi limit'ini tutar. Gerçek distributed limit için yukarıdaki Lua-based sliding window örneğini kullan.