Aracılığıyla paylaş


.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. HttpClient'ın "www.example.com" kaynağına eriştiğini 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ıza oran sınırlaması getirerek uygulamanızın güvenli sayıda isteği işlemesine olanak sağlayın. 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.

DelegatingHandler alt sınıfı uygulama

İstek akışını denetlemek için özel bir DelegatingHandler alt sınıfı uygularsınız. Bu, istekleri sunucuya gönderilmeden önce kesmenize ve işlemenize olanak tanıyan bir HttpMessageHandler türüdür. Yanıtları, çağırana döndürülmeden önce yakalayabilir ve işleyebilirsiniz. Bu örnekte, tek bir kaynağa gönderilebilen istek sayısını sınırlayan özel bir DelegatingHandler alt sınıfı uygulayacaksınız. Aşağıdaki özel ClientSideRateLimitedHandler sınıfını 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:

  • DelegatingHandler türünü devralır.
  • IAsyncDisposable arabirimini uygular.
  • Oluşturucudan atanan bir RateLimiter alanı tanımlar.
  • sunucuya gönderilmeden önce istekleri kesmek ve işlemek için SendAsync yöntemini geçersiz kılar.
  • DisposeAsync() örneğini atmak için RateLimiter yöntemini geçersiz kılar.

SendAsync yöntemine biraz daha yakından baktığınızda şunları görürsünüz:

  • Bu, RateLimiter\'den bir RateLimitLease elde etmek için AcquireAsync örneğine dayanır.
  • lease.IsAcquired özelliği trueolduğunda istek sunucuya gönderilir.
  • Aksi takdirde, HttpResponseMessage durum koduyla bir 429 döndürülür ve leaseRetryAfter bir değer içeriyorsa, Retry-After üst bilgisi bu değere ayarlanır.

Birçok eşzamanlı isteği simule etme

Bu özel DelegatingHandler alt sınıfını test etmek için birçok eşzamanlı isteği simüle eden bir konsol uygulaması oluşturacaksınız. Bu Program sınıfı, özel HttpClientile bir ClientSideRateLimitedHandler 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, 8belirteç sınırı ve OldestFirstkuyruk işleme sırası, 3kuyruk sınırı ve 1 milisaniyelik stok yenileme dönemi, 2dönem başına belirteçler ve trueotomatik yenileme değeri ile yapılandırılır.
  • HttpClientile yapılandırılan ClientSideRateLimitedHandler ile bir TokenBucketRateLimiter oluşturulur.
  • 100 isteği taklit etmek için Enumerable.Range, her birinde benzersiz bir sorgu dizesi parametresi olan 100 URL oluşturur.
  • Task yönteminden iki Parallel.ForEachAsync nesnesi atanır ve URL'ler iki gruba bölünür.
  • HttpClient her URL'ye bir GET isteği göndermek için kullanılır ve yanıt konsola yazılır.
  • Task.WhenAll her iki görevin tamamlanmasını bekler.

HttpClient ClientSideRateLimitedHandlerile yapılandırıldığından, tüm istekler sunucu kaynağına ulaşamayabilir. 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 429HTTP durum koduyla reddedildiğini göreceksiniz. Sunucuya gönderilen istek sayısının nasıl değiştiğini görmek için options oluşturmak için kullanılan TokenBucketRateLimiter 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, oran sınırının tüm istemciler arasında tutarlı bir şekilde uygulandığını da gösterir.

Ayrıca her URL'nin sorgu dizesinin benzersiz olduğunu unutmayın: her istek için bir artırıldığını görmek için iteration parametresini inceleyin. 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 uygulamasını kabul etmek için bu kodu yeniden yazmayı deneyin. TokenBucketRateLimiter ek olarak aşağıdakileri deneyebilirsiniz:

Özet

Bu makalede, özel bir ClientSideRateLimitedHandler'ı nasıl uygulayacağınızı öğ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.

Ayrıca bkz.

  • .NET için Hız Sınırlandırma duyurusu
  • ASP.NET Core'da Hız sınırlama ara yazılımı
  • Azure Mimarisini : Hız sınırlama düzeni
  • .NET'te otomatik yeniden deneme mantığı