Megosztás a következőn keresztül:


HTTP-kezelő sebességkorlátozása a .NET-ben

Ebben a cikkben megtudhatja, hogyan hozhat létre ügyféloldali HTTP-kezelőt, amely korlátozza az általa küldött kérések számát. Megjelenik egy olyan, HttpClient amely hozzáfér az erőforráshoz "www.example.com" . Az erőforrásokat azok az alkalmazások használják fel, amelyek támaszkodnak rájuk, és ha egy alkalmazás túl sok kérést küld egyetlen erőforráshoz, az erőforrás-versengéshez vezethet. Az erőforrás-versengés akkor fordul elő, ha egy erőforrást túl sok alkalmazás használ fel, és az erőforrás nem tudja kiszolgálni az összes alkalmazást, amely kéri. Ez gyenge felhasználói élményt eredményezhet, és bizonyos esetekben akár szolgáltatásmegtagadási (DoS-) támadáshoz is vezethet. További információ a DoS-ról: OWASP: Szolgáltatásmegtagadás.

Mi az a sebességkorlátozás?

A sebességkorlátozás az erőforrás elérésének korlátozására vonatkozik. Előfordulhat például, hogy az alkalmazás által elért adatbázisok percenként 1000 kérést képesek biztonságosan kezelni, de ennél sokkal többet nem. Elhelyezhet egy sebességkorlátozót az alkalmazásban, amely percenként csak 1000 kérést engedélyez, és elutasítja a további kéréseket, mielőtt hozzáférnének az adatbázishoz. Így a sebesség korlátozza az adatbázist, és lehetővé teszi az alkalmazás számára, hogy biztonságos számú kérést kezeljen. Ez egy gyakori minta az elosztott rendszerekben, ahol előfordulhat, hogy egy alkalmazás több példánya is fut, és biztosítani szeretné, hogy ne egyszerre próbálják meg elérni az adatbázist. A kérések folyamatának szabályozásához több különböző sebességkorlátozó algoritmus is létezik.

A sebességkorlátozás .NET-ben való használatához a System.Threading.RateLimiting NuGet-csomagra kell hivatkoznia.

DelegatingHandler Alosztály implementálása

A kérések folyamatának szabályozásához egyéni DelegatingHandler alosztályt kell implementálnia. Ez a típus HttpMessageHandler lehetővé teszi a kérések elfogását és kezelését, mielőtt elküldené őket a kiszolgálóra. A válaszok elfoghatók és kezelhetők, mielőtt visszakerülnének a hívóhoz. Ebben a példában egy egyéni DelegatingHandler alosztályt fog implementálni, amely korlátozza az egyetlen erőforrásnak küldhető kérések számát. Vegye figyelembe a következő egyéni ClientSideRateLimitedHandler osztályt:

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();
        }
    }
}

Az előző C# kód:

  • Örökli a típust DelegatingHandler .
  • Implementálja a IAsyncDisposable felületet.
  • A konstruktortól hozzárendelt RateLimiter mezőt definiál.
  • Felülbírálja a SendAsync kérések elfogására és kezelésére vonatkozó módszert, mielőtt elküldené őket a kiszolgálónak.
  • Felülbírálja a DisposeAsync() példány elidegenítési módszerét RateLimiter .

Egy kicsit közelebbről megvizsgálva a SendAsync metódust, látni fogja, hogy a következő:

  • A példányra támaszkodik, RateLimiter hogy beolvasson egy példányt RateLimitLease a AcquireAsync.
  • Ha a lease.IsAcquired tulajdonság értéke, truea rendszer elküldi a kérést a kiszolgálónak.
  • Ellenkező esetben a HttpResponseMessage rendszer egy állapotkóddal 429 ad vissza egy értéket, és ha a lease fejléc tartalmaz egy RetryAfter értéket, akkor a Retry-After fejléc erre az értékre van állítva.

Több egyidejű kérés emulálása

Ha ezt az egyéni DelegatingHandler alosztályt szeretné tesztelni, létre fog hozni egy konzolalkalmazást, amely számos egyidejű kérést emulál. Ez az Program osztály létrehoz egy HttpClient egyéni ClientSideRateLimitedHandler:

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})");
}

Az előző konzolalkalmazásban:

  • A TokenBucketRateLimiterOptions rendszer a tokenkorlátot 8és a várólista feldolgozási sorrendjét OldestFirst, az ezredmásodperc várakozási időkorlátját 3és feltöltési 1 időtartamát, az időszakonkénti 2jogkivonatokat és az automatikus feltöltési trueértéket adja meg.
  • A létrejön egy HttpClient , a ClientSideRateLimitedHandler következővel konfigurált beállítással TokenBucketRateLimiter: .
  • 100 kérés Enumerable.Range emulálásához 100 URL-címet hoz létre, amelyek mindegyike egyedi lekérdezési sztringparaméterrel rendelkezik.
  • A Task metódus két Parallel.ForEachAsync objektumot rendel hozzá, és az URL-címeket két csoportra osztja.
  • A HttpClient rendszer kérést GET küld minden URL-címre, a válasz pedig a konzolra lesz írva.
  • Task.WhenAll vár, amíg mindkét tevékenység befejeződik.

Mivel a rendszer konfigurálva HttpClient van a ClientSideRateLimitedHandlerkiszolgálói erőforrással, nem minden kérés érkezik a kiszolgálói erőforráshoz. Ezt az állítást a konzolalkalmazás futtatásával tesztelheti. Látni fogja, hogy a rendszer csak a kérések teljes számának töredékét küldi el a kiszolgálónak, a többit pedig a rendszer a http-állapotkóddal 429utasítja el. Próbálja meg módosítani a options kiszolgálónak küldött kérések számának változását a létrehozáshoz TokenBucketRateLimiter használt objektumon.

Vegye figyelembe a következő példakimenetet:

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)

Megfigyelheti, hogy az első naplózott bejegyzések mindig az azonnal visszaadott 429 válasz, az utolsó bejegyzések pedig mindig a 200 válasz. Ennek az az oka, hogy a sebességkorlát ügyféloldali, és elkerüli, hogy HTTP-hívást kezdeményezzenek a kiszolgálóhoz. Ez azért jó, mert azt jelenti, hogy a kiszolgálót nem elárasztják a kérések. Azt is jelenti, hogy a sebességkorlátot minden ügyfél következetesen érvényesíti.

Vegye figyelembe, hogy az egyes URL-címek lekérdezési sztringje egyedi: vizsgálja meg a iteration paramétert, és ellenőrizze, hogy az egyes kérések esetében egy-egy lépéssel van-e növelve. Ez a paraméter segít szemléltetni, hogy a 429 válasz nem az első kérésekből származik, hanem a sebességkorlát elérése után küldött kérelmekből. A 200 válasz később érkezik, de ezek a kérések korábban érkeztek – a korlát elérése előtt.

A különböző sebességkorlátozó algoritmusok jobb megértéséhez próbálja meg újraírni ezt a kódot, hogy elfogadjon egy másik RateLimiter implementációt. Amellett, hogy TokenBucketRateLimiter megpróbálhatja:

  • ConcurrencyLimiter
  • FixedWindowRateLimiter
  • PartitionedRateLimiter
  • SlidingWindowRateLimiter

Összegzés

Ebben a cikkben megtanulta, hogyan implementálhat egyénit ClientSideRateLimitedHandler. Ez a minta használható egy korlátozott sebességű HTTP-ügyfél implementálásához olyan erőforrásokhoz, amelyekről tudja, hogy API-korlátokkal rendelkeznek. Ily módon megakadályozza, hogy az ügyfélalkalmazás szükségtelen kéréseket küldjön a kiszolgálónak, és azt is megakadályozza, hogy a kiszolgáló letiltsa az alkalmazást. Emellett az újrapróbálkozás időzítési értékeinek tárolására szolgáló metaadatok használatával automatikus újrapróbálkoztatási logikát is implementálhat.

Lásd még