API Rate Limiting ve Throttling Best Practices
API'lerinizi aşırı kullanımdan korumak ve hizmet kalitesini (QoS) garanti altına almak için Rate Limiting (Hız Sınırlama) ve Throttling (Boğma), olmazsa olmaz iki savunma mekanizmasıdır. Bu yazıda, en yaygın kullanılan algoritmaları, .NET'teki uygulama yöntemlerini ve sağlam bir strateji için dikkat edilmesi gereken en iyi pratikleri inceleyeceğiz.
1. Temel Rate Limiting Algoritmaları
Doğru algoritmayı seçmek, uygulamanızın trafik desenine ve ihtiyaçlarına bağlıdır:
| Algoritma | Çalışma Mantığı | Avantajları | Dezavantajları | Kullanım Alanı |
|---|---|---|---|---|
| Token Bucket | Sabit kapasiteli bir kovaya belirli hızda token eklenir; her istek bir token tüketir. | Ani trafik patlamalarına (burst) izin verir. | Dağıtık sistemlerde token durumunu paylaşmak ek maliyet getirir. | Genel API limitleri, kullanıcı başına istek sınırı. |
| Leaky Bucket | Gelen istekler sabit kapasiteli kovaya eklenir ve sabit hızla işlenir (sızdırır). | Trafik akışını düzgünleştirir (smoothing). | Ani patlamaları desteklemez; kuyruk yönetimi karmaşıktır. | Veritabanı yazma, batch işlemler. |
| Fixed Window | Zaman sabit aralıklara bölünür ve her aralık için bir sayaç tutulur. | Çok basit ve hafiftir. | Pencere sınırlarında limitin iki katı istek gönderilebilir (sınır sorunu). | Basit, düşük trafikli API'ler. |
| Sliding Window | Zamanı daha hassas parçalara bölerek veya isteklerin zaman damgalarını tutarak çalışır. | Sınır sorununu ortadan kaldırır, daha adildir. | Uygulaması daha zordur ve daha fazla bellek tüketir. | Yüksek trafik, hassas limit ihtiyacı. |
2. .NET'te Rate Limiting Uygulama Yöntemleri
.NET 7 ve sonrası, System.Threading.RateLimiting namespace'i ile yerleşik ve güçlü bir rate limiting desteği sunmaktadır.
A. Yerleşik Rate Limiter'ları Kullanmak (.NET 7+)
En temel ve önerilen yöntem, bu yerleşik limiter'ları kullanmaktır:
csharp
// Program.cs
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(options =>
{
// 1. Fixed Window Limiter: Dakikada 100 istek
options.AddFixedWindowLimiter("Fixed", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromMinutes(1);
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 0; // Kuyruk yok
});
// 2. Sliding Window Limiter: Dakikada 100 istek, 10 segment
options.AddSlidingWindowLimiter("Sliding", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromMinutes(1);
opt.SegmentsPerWindow = 10;
});
// 3. Token Bucket Limiter
options.AddTokenBucketLimiter("TokenBucket", opt =>
{
opt.TokenLimit = 100;
opt.QueueLimit = 10;
opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
opt.TokensPerPeriod = 20;
});
// 4. Global veya Endpoint bazında uygulama
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(
httpContext => RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.User.Identity?.Name ?? httpContext.Connection.RemoteIpAddress?.ToString(),
factory: partition => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
}
));
});
app.UseRateLimiter(); // Middleware'i ekle
app.MapGet("/api/data", () => "Hello, World!")
.RequireRateLimiting("Fixed"); // Belirli bir policy kullan
Bu yaklaşım, yukarıdaki algoritmaların çoğunu zaten implemente ettiği için hızlı ve güvenilir bir çözüm sunar.
B. Özel Middleware ile İnce Ayar (Önceki Sürümler veya Özel İhtiyaçlar)
Eğer .NET 7 öncesi bir sürüm kullanıyorsanız veya çok özel bir mantık gerekiyorsa, özel bir middleware oluşturabilirsiniz:
csharp
public class RateLimitingMiddleware
{
private readonly RequestDelegate _next;
private readonly IMemoryCache _cache;
public async Task InvokeAsync(HttpContext context)
{
var clientId = context.Connection.RemoteIpAddress.ToString();
var cacheKey = $"RateLimit_{clientId}";
// 1. Cache'ten mevcut sayaç değerini al
if (_cache.TryGetValue(cacheKey, out int requestCount))
{
if (requestCount >= 100) // Limit aşıldıysa
{
context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
await context.Response.WriteAsync("Rate limit exceeded. Please try again later.");
return;
}
// Limit aşılmadıysa sayacı artır
_cache.Set(cacheKey, requestCount + 1, TimeSpan.FromMinutes(1));
}
else
{
// İlk istek, sayacı oluştur
_cache.Set(cacheKey, 1, TimeSpan.FromMinutes(1));
}
await _next(context);
}
}
3. Dağıtık Sistemlerde Rate Limiting (Redis ile)
Birden fazla sunucu (Web Farm, Kubernetes) varsa, istemciler farklı sunuculara istek gönderebilir. Bu durumda, rate limiter state'ini dağıtık bir cache'de (Redis) saklamak gerekir.
-
Redis Sorted Set: Her isteği bir sorted set'e ekleyip penceredeki istek sayısını hesaplamak.
-
Redis Lua Script: Atomik işlem için Lua script kullanarak hem okuma hem yazma yapmak (race condition önleme).
Örnek (Redis Sorted Set ile Sliding Window):
csharp
public async Task<bool> IsAllowedAsync(string key, int limit, TimeSpan window)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var windowStart = now - (long)window.TotalSeconds;
var redis = GetRedisDatabase(); // StackExchange.Redis
var sortedSetKey = $"ratelimit:{key}";
// 1. Pencere dışındaki eski istekleri temizle
await redis.SortedSetRemoveRangeByScoreAsync(sortedSetKey, 0, windowStart);
// 2. Mevcut penceredeki istek sayısını al
var count = await redis.SortedSetLengthAsync(sortedSetKey);
// 3. Limit kontrolü
if (count < limit)
{
// 4. Yeni isteği ekle (score = timestamp)
await redis.SortedSetAddAsync(sortedSetKey, Guid.NewGuid().ToString(), now);
await redis.KeyExpireAsync(sortedSetKey, window); // TTL
return true;
}
return false;
}
Dağıtık Rate Limiting Kütüphaneleri: AspNetCoreRateLimit (Redis desteği), Polly.RateLimit gibi hazır çözümler de mevcuttur.
4. Rate Limiting En İyi Pratikler (Best Practices)
-
Doğru Algoritmayı Seçin: Trafik deseninize göre karar verin. Çoğu genel senaryo için Token Bucket iyi bir dengedir.
-
Limitleri Dinamik Yapın: Kullanıcı abonelik seviyesine göre limitler değişebilir. Partition key'i doğru seçin (ör. API Key) ve limitleri bir konfigürasyon dosyasından veya veritabanından okuyun.
-
Kullanıcı Deneyimini Ön Planda Tutun: İstek reddedildiğinde (
429 Too Many Requests) yanıt başlığınaRetry-Afterbilgisini ekleyerek istemciye ne zaman tekrar deneyeceğini bildirin. -
Rate Limiting'i Sadece Gateway'de Değil, Servis Katmanında da Uygulayın: Bazı servisler doğrudan gateway dışından çağrılabilir. Bu nedenle, rate limiting'i hem gateway'de hem de servis katmanında (opsiyonel) uygulayın.
-
Token Bucket'ta Replenishment Period'u İyi Ayarlayın: Token'ları saniyede 10 eklemek yerine, toplu olarak (ör. 60 saniyede 600 token) eklemek, sürekli yenileme maliyetini azaltır.
-
İzleme ve Uyarı (Monitoring & Alerting) Kurun: Rate limiting ile ilgili metrikleri (toplam istek, reddedilen istek, limit aşımı) izleyin ve anormal durumlar için uyarılar oluşturun.
Sonuç
Rate limiting, API'larınızın güvenliğini, kararlılığını ve adil kullanımını sağlamak için kritik bir araçtır. .NET 7+ ile gelen yerleşik RateLimiter desteği, bu mekanizmayı uygulamayı her zamankinden daha kolay hale getirmiştir. Doğru algoritmayı seçmek, dağıtık sistemlerde Redis gibi araçlarla state yönetimini sağlamak ve yukarıdaki en iyi pratikleri takip etmek, başarılı bir rate limiting stratejisinin anahtarıdır. Unutmayın, hızlı ve adil bir API, mutlu kullanıcılar demektir.