RateLimiter limitowanie ilości żądań

Wprowadzenie

Tworząc aplikację, na ogół zależy nam na tym, aby działała jak najszybciej. Spędzamy często wiele godzin na tym, aby zoptymalizować jej działanie. Ale sporadycznie pojawiają się sytuacje, kiedy musimy sztucznie ograniczyć ilość operacji. Na przykład API, z którego korzystamy, umożliwia wykonanie tylko ograniczonej ilości żądań w jednostce czasu.

Biblioteka RateLimiter, o której mowa w tym wpisie, właśnie umożliwia zrealizowanie czegoś takiego w aplikacji .NET. Co fajne, nie ogranicza się tylko do samych żądań HTTP, ale również możemy ją wykorzystać przy dostępie do innych zasobów. A do tego można zintegrować ją z biblioteką Flurl, o której było jakiś czas temu na blogu.

RateLimiter

Użycie biblioteki RateLimiter (https://github.com/David-Desmaisons/RateLimiter) jest dość proste. Po dodaniu jej z wykorzystaniem nuget, wystarczy, że skorzystamy z klasy TimeLimiter, która udostępnia metodę GetFromMaxCountByInterval. Do metody tej przekazujemy maksymalną ilość operacji oraz interwał czasu (TimeSpan), co widać na przykładzie:

static async Task ConsoleExample()
{
var timeConstraint = TimeLimiter.GetFromMaxCountByInterval(5, TimeSpan.FromSeconds(1));
for (int i = 0; i < 100; i++)
{
await timeConstraint;
Console.WriteLine($"{DateTime.Now:MM/dd/yyy HH:mm:ss.fff}");
}
}
view raw ConsoleExample.cs hosted with ❤ by GitHub

Na powyższym fragmencie kodu widać, że RateLimiter możemy użyć do limitowania dostępu do różnych zasobów. Metoda GetFromMaxCountByInterval zwraca nam obiekt, na którym po prostu używamy słowa kluczowego await. W momencie osiągnięcia limitu operacji await w sposób asynchroniczny poczeka do końca okresu czasu i później wykona kolejne operacje.

Powyższy kod wyświetla aktualną datę na konsoli, ale nie więcej niż 5 razy na sekundę, co widać na poniższym zrzucie:

Wynik działa RareLimiter w aplikacji konsolowej

Dzięki takiej konstrukcji działania biblioteki możemy ją używać w różnych miejscach aplikacji, nie tylko podczas wykonywania żądań HTTP.

Darmowy kurs Visual Studio

Pracując z setkami programistów, zauważyłem, że większość osób nie pracuje efektywnie w Visual Studio. W skrajnych przypadkach korzystali z kopiowania z wykorzystaniem menu Edit. Wiem, że to dziwne, ale naprawdę niektórzy tak pracują. Dlatego postanowiłem stworzyć kurs Visual Studio – aby pomóc koleżankom i kolegom w efektywniejszej pracy.

Przygotowałem 30 lekcji e-mail, w których pokażę Ci, w jaki sposób pracować efektywniej i szybciej w Visual Studio. Poznasz dodatki, bez których nie wyobrażam sobie pracy w tym IDE.

Po więcej informacji zapraszam na dedykowaną stronę kursu: Darmowy Kurs Visual Studio.

Quiz C#

Ostatnio przygotowałem również quiz C#, w którym możesz sprawdzić swoją wiedzę. Podejmiesz wyzwanie?

RateLimiter a HttpClient

Na ogół bibliotekę RateLimiter będziemy używali podczas wykonywania żądań HTTP. W tym przypadku możemy użyć biblioteki w trochę inny sposób, co widać na przykładzie:

static async Task HttpClientExample()
{
var handler = TimeLimiter
.GetFromMaxCountByInterval(5, TimeSpan.FromSeconds(10))
.AsDelegatingHandler();
var client = new HttpClient(handler);
for (int i = 0; i < 20; i++)
{
var content = await client.GetStringAsync("https://visualstudio.plawgo.pl");
Console.WriteLine($"Page downloaded: {content.Length}");
}
}

W tym przykładzie korzystamy z dodatkowej metody o nazwie AsDelegatingHandler, która, jak sama nazwa mówi, zwraca DelegatingHandler. Zwrócony obiekt przekazujemy do konstruktora klasy HttpClient. Obiekt ten będzie zarządzał wykonywaniem żądań przez HttpClienta i odpowiednim momencie będzie blokował wykonywanie kolejnych żądań.

Konstrukcja ta jest o tyle fajniejsza, że możemy ją wykorzystać w momencie, gdy w aplikacji mamy wiele klas wykonujących żądania do tego samego API. Wtedy możemy zarejestrować jedną instancję klasy HttpClient z konfiguracją RateLimitera w kontenerze dependency injection i później wstrzykiwać ją do tych wszystkich klas.

Dzięki temu nie będziemy musieli pisać dodatkowego kodu w celu globalnego pilnowania limitów API.

RateLimiter a Flulr

Jakiś czas temu na blogu opisywałem bibliotekę Flurl, która służy między innymi do łatwiejszego wykonywania żądań HTTP. Co fajne jesteśmy w stanie również skonfigurować RareLimitera z Flulr.

Flulr to tak naprawdę obudowanie HttpClienta i sama biblioteka udostępnia interfejs IHttpClientFactory, za pomocą którego możemy dodać własną logikę, która ma zostać wykonana podczas tworzenia nowej instancji HttpClienta. Dzięki temu możemy w tym miejscu użyć bibliotekę RateLimiter.

Tworząc własną fabrykę HttpClienta, najlepiej skorzystać z dostępnej fabryki i nadpisać jej metodę CreateMessageHandler:

public class HttpClientFactory : DefaultHttpClientFactory
{
public override HttpMessageHandler CreateMessageHandler()
{
var handler = TimeLimiter
.GetFromMaxCountByInterval(5, TimeSpan.FromSeconds(10))
.AsDelegatingHandler();
handler.InnerHandler = base.CreateMessageHandler();
return handler;
}
}

W metodzie tej tworzymy konfigurację RateLimitera z wykorzystaniem DelegationHandlera. Aby nie stracić części funkcjonalności, które udostępnia Flurl (biblioteka wykorzystuje własne mechanizmy do zarządzania przekierowaniami oraz plikami cookie), wywołujemy bazową metodę CreateMessageHandler i zwrócony obiekt przekazujemy do InnerHandler obiektu zwróconego przez RateLimitera, dzięki czemu będziemy mieli funkcjonalności z obu bibliotek.

Tak utworzoną fabrykę możemy wykorzystać globalnie w całej aplikacji poprzez statyczną metodę Configure z klasy FlurlHttp.

static async Task FlurlGlobalExample()
{
FlurlHttp.Configure(settings => {
settings.HttpClientFactory = new HttpClientFactory();
});
var url = "https://visualstudio.plawgo.pl";
for (int i = 0; i < 20; i++)
{
var content = await url.GetStringAsync();
Console.WriteLine($"Page downloaded: {content.Length}");
}
}

Lub lokalną konfigurację poprzez wykorzystanie klasy FlurlClient:

static async Task FlurlClientExample()
{
var url = "https://visualstudio.plawgo.pl";
var cli = new FlurlClient(url).Configure(settings => {
settings.HttpClientFactory = new HttpClientFactory();
});
for (int i = 0; i < 20; i++)
{
var content = await cli.Request().GetStringAsync();
Console.WriteLine($"Page downloaded: {content.Length}");
}
}

W większości przypadków będziemy korzystali z drugiego rozwiązania, w którym będziemy mieli dedykowanego klienta dla danego API (bazowy adres url) zarejestrowanego w kontenerze dependency injection.

Przykład

Na githubie (https://github.com/danielplawgo/RateLimiterTests) znajduje się przykład do tego wpisu. Po jego pobraniu można go od razu uruchomić i bawić się. Nie jest potrzebna dodatkowo konfiguracja. Poszczególne przykład można uruchamiać odkomentowując wywołania metod w metodzie Main.

Podsumowanie

RateLimiter jest prostą w użyciu biblioteką, która przydaje się w tych nielicznych sytuacjach, gdy chcemy ograniczyć w szczególności ilość żądań wysyłanych do jakiegoś API. Dodatkowo można w miarę łatwo to zintegrować z Flurlem, którego używam na co dzień.

1 thought on “RateLimiter limitowanie ilości żądań

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *