.NET'te HTTP işleyicisi hız sınırı
Bu makalede, gönderdiği istek sayısını sınırlayan bir istemci tarafı HTTP işleyicisi oluşturmayı öğreneceksiniz. Kaynağa erişen "www.example.com"
bir HttpClient görürsünüz. Kaynaklar, bu kaynaklara dayanan uygulamalar tarafından kullanılır ve bir uygulama tek bir kaynak için çok fazla istekte bulunursa kaynak çekişmesine yol açabilir. Kaynak çekişmesi, bir kaynak çok fazla uygulama tarafından kullanıldığında ve kaynak bunu isteyen tüm uygulamalara hizmet veremediğinde oluşur. Bu, kötü bir kullanıcı deneyimine neden olabilir ve bazı durumlarda hizmet reddi (DoS) saldırısına bile yol açabilir. DoS hakkında daha fazla bilgi için bkz . OWASP: Hizmet Reddi.
Hız sınırlama nedir?
Hız sınırlama, bir kaynağa ne kadar erişilebileceğini sınırlama kavramıdır. Örneğin, uygulamanızın eriştiği bir veritabanının dakikada 1.000 isteği güvenli bir şekilde işleyebileceğini biliyor olabilirsiniz, ancak bundan çok daha fazlasını işlemeyebilir. Uygulamanıza dakikada yalnızca 1.000 isteğe izin veren ve veritabanına erişebilmeleri için daha fazla isteği reddeden bir hız sınırlayıcısı koyabilirsiniz. Bu nedenle, veritabanınızı sınırlayan ve uygulamanızın güvenli sayıda isteği işlemesine izin veren hız. Bu, dağıtılmış sistemlerde yaygın olarak kullanılan bir desendir; burada bir uygulamanın birden çok örneği çalışıyor olabilir ve hepsinin aynı anda veritabanına erişmeye çalışmadığından emin olmak istersiniz. İstek akışını denetlemek için birden çok farklı hız sınırlama algoritması vardır.
.NET'te hız sınırlamayı kullanmak için System.Threading.RateLimiting NuGet paketine başvuracaksınız.
Alt sınıf uygulama DelegatingHandler
İstek akışını denetlemek için özel DelegatingHandler bir alt sınıf uygularsınız. Bu, istekleri sunucuya gönderilmeden önce kesmenize ve işlemenize olanak tanıyan bir türüdür HttpMessageHandler . Ayrıca, yanıtlar araya girip çağırana döndürülmeden önce de işleyebilirsiniz. Bu örnekte, tek bir kaynağa gönderilebilen istek sayısını sınırlayan özel DelegatingHandler
bir alt sınıf uygulayacaksınız. Aşağıdaki özel ClientSideRateLimitedHandler
sınıfı göz önünde bulundurun:
internal sealed class ClientSideRateLimitedHandler(
RateLimiter limiter)
: DelegatingHandler(new HttpClientHandler()), IAsyncDisposable
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
using RateLimitLease lease = await limiter.AcquireAsync(
permitCount: 1, cancellationToken);
if (lease.IsAcquired)
{
return await base.SendAsync(request, cancellationToken);
}
var response = new HttpResponseMessage(HttpStatusCode.TooManyRequests);
if (lease.TryGetMetadata(
MetadataName.RetryAfter, out TimeSpan retryAfter))
{
response.Headers.Add(
"Retry-After",
((int)retryAfter.TotalSeconds).ToString(
NumberFormatInfo.InvariantInfo));
}
return response;
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
await limiter.DisposeAsync().ConfigureAwait(false);
Dispose(disposing: false);
GC.SuppressFinalize(this);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
limiter.Dispose();
}
}
}
Yukarıdaki C# kodu:
- Türü devralır
DelegatingHandler
. - Arabirimini IAsyncDisposable uygular.
- Oluşturucudan atanan bir
RateLimiter
alanı tanımlar. - İstekleri
SendAsync
sunucuya gönderilmeden önce araya girip işlemek için yöntemini geçersiz kılar. - DisposeAsync() Örneği atmak
RateLimiter
için yöntemini geçersiz kılar.
yöntemine SendAsync
biraz daha yakından baktığınızda şunu görürsünüz:
- 'den bir
RateLimitLease
almak için örneğineAcquireAsync
dayanırRateLimiter
. lease.IsAcquired
özelliği olduğundatrue
istek sunucuya gönderilir.- Aksi takdirde, bir HttpResponseMessage
429
durum kodu ile döndürülür velease
değeri içeriyorsaRetryAfter
,Retry-After
üst bilgi bu değere ayarlanır.
Birçok eşzamanlı isteği öykünme
Bu özel DelegatingHandler
alt sınıfı test etmek için birçok eşzamanlı isteği öykünen bir konsol uygulaması oluşturacaksınız. Bu Program
sınıf, özel ClientSideRateLimitedHandler
ile bir HttpClient oluşturur:
var options = new TokenBucketRateLimiterOptions
{
TokenLimit = 8,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 2,
AutoReplenishment = true
};
// Create an HTTP client with the client-side rate limited handler.
using HttpClient client = new(
handler: new ClientSideRateLimitedHandler(
limiter: new TokenBucketRateLimiter(options)));
// Create 100 urls with a unique query string.
var oneHundredUrls = Enumerable.Range(0, 100).Select(
i => $"https://example.com?iteration={i:0#}");
// Flood the HTTP client with requests.
var floodOneThroughFortyNineTask = Parallel.ForEachAsync(
source: oneHundredUrls.Take(0..49),
body: (url, cancellationToken) => GetAsync(client, url, cancellationToken));
var floodFiftyThroughOneHundredTask = Parallel.ForEachAsync(
source: oneHundredUrls.Take(^50..),
body: (url, cancellationToken) => GetAsync(client, url, cancellationToken));
await Task.WhenAll(
floodOneThroughFortyNineTask,
floodFiftyThroughOneHundredTask);
static async ValueTask GetAsync(
HttpClient client, string url, CancellationToken cancellationToken)
{
using var response =
await client.GetAsync(url, cancellationToken);
Console.WriteLine(
$"URL: {url}, HTTP status code: {response.StatusCode} ({(int)response.StatusCode})");
}
Önceki konsol uygulamasında:
TokenBucketRateLimiterOptions
, belirteç sınırı8
ve kuyruk işleme sırasıOldestFirst
, bir kuyruk sınırı3
ve milisaniyelik yenileme süresi1
, dönem başına belirteç değeri2
ve değerinin otomatik yenileme değeritrue
ile yapılandırılır.HttpClient
ileClientSideRateLimitedHandler
yapılandırılan ileTokenBucketRateLimiter
oluşturulur.- 100 isteği öykünmek için, Enumerable.Range her birinin benzersiz bir sorgu dizesi parametresine sahip olan 100 URL oluşturur.
- yönteminden Parallel.ForEachAsync iki Task nesne atanır ve URL'ler iki gruba bölünür.
HttpClient
her URL'ye istekGET
göndermek için kullanılır ve yanıt konsola yazılır.- Task.WhenAll her iki görevi de tamamlamayı bekler.
HttpClient
ile yapılandırıldığındanClientSideRateLimitedHandler
, tüm istekler sunucu kaynağına yapamaz. Konsol uygulamasını çalıştırarak bu onaylama işlemini test edebilirsiniz. Sunucuya toplam istek sayısının yalnızca bir kısmının gönderildiğini ve geri kalanının HTTP durum koduyla 429
reddedildiğini göreceksiniz. Sunucuya gönderilen isteklerin options
sayısının nasıl değiştiğini görmek için öğesini oluşturmak TokenBucketRateLimiter
için kullanılan nesnesini değiştirmeyi deneyin.
Aşağıdaki örnek çıkışı göz önünde bulundurun:
URL: https://example.com?iteration=06, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=60, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=55, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=59, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=57, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=11, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=63, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=13, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=62, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=65, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=64, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=67, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=14, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=68, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=16, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=69, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=70, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=71, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=17, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=18, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=72, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=73, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=74, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=19, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=75, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=76, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=79, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=77, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=21, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=78, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=81, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=22, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=80, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=20, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=82, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=83, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=23, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=84, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=24, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=85, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=86, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=25, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=87, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=26, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=88, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=89, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=27, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=90, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=28, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=91, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=94, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=29, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=93, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=96, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=92, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=95, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=31, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=30, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=97, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=98, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=99, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=32, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=33, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=34, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=35, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=36, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=37, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=38, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=39, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=40, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=41, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=42, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=43, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=44, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=45, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=46, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=47, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=48, HTTP status code: TooManyRequests (429)
URL: https://example.com?iteration=15, HTTP status code: OK (200)
URL: https://example.com?iteration=04, HTTP status code: OK (200)
URL: https://example.com?iteration=54, HTTP status code: OK (200)
URL: https://example.com?iteration=08, HTTP status code: OK (200)
URL: https://example.com?iteration=00, HTTP status code: OK (200)
URL: https://example.com?iteration=51, HTTP status code: OK (200)
URL: https://example.com?iteration=10, HTTP status code: OK (200)
URL: https://example.com?iteration=66, HTTP status code: OK (200)
URL: https://example.com?iteration=56, HTTP status code: OK (200)
URL: https://example.com?iteration=52, HTTP status code: OK (200)
URL: https://example.com?iteration=12, HTTP status code: OK (200)
URL: https://example.com?iteration=53, HTTP status code: OK (200)
URL: https://example.com?iteration=07, HTTP status code: OK (200)
URL: https://example.com?iteration=02, HTTP status code: OK (200)
URL: https://example.com?iteration=01, HTTP status code: OK (200)
URL: https://example.com?iteration=61, HTTP status code: OK (200)
URL: https://example.com?iteration=05, HTTP status code: OK (200)
URL: https://example.com?iteration=09, HTTP status code: OK (200)
URL: https://example.com?iteration=03, HTTP status code: OK (200)
URL: https://example.com?iteration=58, HTTP status code: OK (200)
URL: https://example.com?iteration=50, HTTP status code: OK (200)
İlk günlüğe kaydedilen girişlerin her zaman hemen döndürülen 429 yanıt olduğunu ve son girişlerin her zaman 200 yanıt olduğunu fark edeceksiniz. Bunun nedeni hız sınırıyla istemci tarafında karşılaşılması ve bir sunucuya HTTP çağrısı yapmaktan kaçınmasıdır. Sunucunun isteklerle dolu olmadığı anlamına geldiği için bu iyi bir şeydir. Bu, hız sınırının tüm istemciler arasında tutarlı bir şekilde zorlandığını da gösterir.
Her URL'nin sorgu dizesinin benzersiz olduğunu da unutmayın: Her istek için bir artırıldığını görmek için parametresini inceleyin iteration
. Bu parametre, 429 yanıtlarının ilk isteklerden değil, hız sınırına ulaşıldıktan sonra yapılan isteklerden geldiğini göstermeye yardımcı olur. 200 yanıt daha sonra gelir, ancak bu istekler sınıra ulaşılamadan önce yapılmıştır.
Çeşitli hız sınırlama algoritmalarını daha iyi anlamak için farklı bir RateLimiter
uygulamayı kabul etmek için bu kodu yeniden yazmayı deneyin. Aşağıdakilere TokenBucketRateLimiter
ek olarak aşağıdakileri deneyebilirsiniz:
ConcurrencyLimiter
FixedWindowRateLimiter
PartitionedRateLimiter
SlidingWindowRateLimiter
Özet
Bu makalede, özel ClientSideRateLimitedHandler
bir uygulamayı öğrendiniz. Bu desen, API sınırları olduğunu bildiğiniz kaynaklar için hız sınırlı bir HTTP istemcisi uygulamak için kullanılabilir. Bu şekilde, istemci uygulamanızın sunucuya gereksiz isteklerde bulunmasını engeller ve uygulamanızın sunucu tarafından engellenmesini de engellersiniz. Ayrıca, yeniden deneme zamanlaması değerlerini depolamak için meta verilerin kullanılmasıyla, otomatik yeniden deneme mantığını da uygulayabilirsiniz.