Wyobraź sobie, że twój program musi wysyłać zapytania do zewnętrznego API pogodowego, które pozwala na maksymalnie 100 żądań na godzinę. Co się stanie, jeśli spróbujesz wysłać 200 żądań w ciągu minuty? Najprawdopodobniej część z nich zostanie odrzucona albo otrzymasz błąd HTTP 429 Too Many Requests – sygnał, że przekroczyłeś dopuszczalny limit. Aby unikać takich sytuacji i chronić zarówno zewnętrzne API, jak i własne aplikacje przed przeciążeniem, stosujemy mechanizm rate limiting. Mówiąc prościej, rate limiting to ograniczanie liczby operacji (np. wywołań API) wykonywanych w określonym czasie, tak aby nie przekroczyć ustalonego limitu. Dzięki temu możemy zadbać o stabilność usług i sprawiedliwe wykorzystanie zasobów.
Dlaczego warto ograniczać tempo żądań?
Mechanizmy rate limiting przydają się w wielu scenariuszach – od bezpieczeństwa po wydajność. Oto kilka głównych powodów, dla których warto kontrolować tempo żądań w aplikacji:
• Ochrona przed przeciążeniem: Limitowanie zapytań zapobiega zalewaniu serwera zbyt dużą liczbą żądań naraz (np. przez błąd w pętli lub atak typu DDoS). Dzięki temu Twoja aplikacja pozostaje responsywna.
• Przestrzeganie limitów zewnętrznych API: Wiele usług (np. API Google, Twittera) narzuca własne limity na liczbę wywołań w danym przedziale czasu. Stosując rate limiting po stronie klienta, unikniesz blokady lub błędów wynikających z przekroczenia tych limitów.
• Sprawiedliwe użycie zasobów: Jeśli z Twojego API korzysta wielu użytkowników, ograniczenie liczby żądań na użytkownika zapobiega sytuacji, w której jeden klient zużywa większość zasobów kosztem innych. Każdy dostaje swoją "działkę" w ustalonym tempie.
Implementacja Rate Limiting w C#
Skoro wiemy, po co nam ograniczanie tempa, zobaczmy jak zaimplementować rate limiting w C#. Mamy kilka podejść – od własnoręcznych rozwiązań po gotowe biblioteki. Poniżej opisuję najpopularniejsze metody:
• Własna implementacja w kodzie: Możesz samodzielnie napisać logikę ograniczającą wywołania. Najprostszy sposób to zliczanie operacji w ustalonym okienku czasowym. Na przykład, możesz założyć, że pozwalasz na 5 wywołań na sekundę – tworzysz licznik wywołań i co sekundę go zerujesz. Gdy licznik przekroczy 5 przed upływem sekundy, kolejne żądania muszą poczekać (lub zostać pominięte). To podejście daje pełną kontrolę, ale wymaga zadbania o wątki (gdy wiele wywołań dzieje się równocześnie) i ręcznego dostosowania algorytmu (np. stałe okno vs. okno przesuwne). Poniżej prosty przykład takiej implementacji fixed window:
int maxCalls = 5;
TimeSpan window = TimeSpan.FromSeconds(1);
int callCount = 0;
DateTime windowStart = DateTime.UtcNow;
void AttemptAction()
{
var now = DateTime.UtcNow;
if (now - windowStart > window)
{
/* Nowe okno czasowe – zresetuj licznik */
callCount = 0;
windowStart = now;
}
if (callCount < maxCalls)
{
callCount++;
/* Tutaj wykonaj właściwą akcję, np. wywołanie API */
Console.WriteLine("Akcja wykonana.");
}
else
{
Console.WriteLine("Zbyt wiele żądań! Poczekaj chwilę.");
/* Ewentualnie: Thread.Sleep(window - (now - windowStart)); */
}
}• Biblioteka Polly (rate limiting po stronie klienta): Zamiast pisać wszystko samemu, możesz skorzystać z gotowych bibliotek. Jedną z popularnych jest Polly – biblioteka do tworzenia polityk odporności (resilience), która obsługuje m.in. retry, circuit breaker, a także rate limit. Za pomocą Polly możesz ustawić politykę, która pozwoli na określoną liczbę wywołań na sekundę. Gdy limit zostanie przekroczony, Polly rzuci wyjątek lub opóźni kolejne wywołanie – dzięki czemu Twoja aplikacja kliencka nie zbombarduje zewnętrznego serwisu nadmiarem żądań. To wygodne rozwiązanie np. gdy Twój kod wykonuje wiele żądań HTTP i chcesz ograniczyć ich częstotliwość. W praktyce użycie Polly sprowadza się do zdefiniowania polityki (np. Policy.RateLimit) i owrapowania nią wywołań HTTP – biblioteka zajmie się resztą.
• Wbudowane middleware w ASP.NET Core (od .NET 7 wzwyż): Jeśli tworzysz web API w ASP.NET Core, framework udostępnia gotowy mechanizm rate limiting jako middleware. Wystarczy dodać odpowiedni pakiet (np. Microsoft.AspNetCore.RateLimiting) i skonfigurować polityki limitów w kodzie startowym aplikacji. Możesz definiować limity globalne lub per endpoint – np. ograniczyć dany adres URL do 10 żądań na minutę dla jednego użytkownika. .NET 7 i .NET 8 oferują kilka algorytmów do wyboru (takich jak fixed window, sliding window, token bucket czy concurrency limit). Przykładowo, aby zastosować prosty fixed window w API, konfigurujesz w Program.cs coś takiego:
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("myPolicy", opt =>
{
opt.Window = TimeSpan.FromMinutes(1);
opt.PermitLimit = 10; /* maks 10 żądań na okno (minutę) */
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 0; /* brak kolejkowania nadmiarowych żądań */
});
});
app.UseRateLimiter();
app.MapGet("/moje-api", HandleRequest).RequireRateLimiting("myPolicy");Powyższa konfiguracja oznacza: dla endpointu /moje-api zezwalaj maksymalnie na 10 wywołań na minutę. Nadmiarowe żądania zostaną odrzucone automatycznie z kodem HTTP 429. Co ważne, gotowe middleware obsługuje też bardziej zaawansowane scenariusze (np. sliding window, które lepiej radzi sobie z nagłymi burstami żądań, czy token bucket, który dozująco dodaje "tokeny" w czasie). Dzięki temu nie musisz samodzielnie kodować tych algorytmów – wystarczy skonfigurować odpowiednią politykę.
Podsumowanie
Rate limiting to przydatna technika, która pomaga utrzymać nasze aplikacje w ryzach – zapobiega przeciążeniom i pozwala dotrzymywać narzuconych limitów. W C# i .NET możemy zaimplementować ją na różne sposoby: od prostych liczników w kodzie, po użycie dojrzałych narzędzi jak Polly czy wbudowane funkcje platformy. Ważne jest, by świadomie korzystać z tych mechanizmów tam, gdzie są potrzebne (np. przy integracjach z API zewnętrznymi lub w celu ochrony własnego serwisu). Dzięki temu nasze aplikacje działają stabilnie i przewidywalnie, a my śpimy spokojniej wiedząc, że nie grozi nam nagłe przekroczenie limitów.
Na koniec warto dodać, że nauka takich praktycznych zagadnień to świetny krok w rozwoju każdego początkującego programisty. Jeśli chcesz systematycznie opanować podobne tematy i pewnie wejść w świat C#/.NET, zachęcam do sprawdzenia mojego kursu online "Zostań Programistą .NET" – to kompletny program szkoleniowy, który prowadzi od podstaw aż do pierwszej pracy jako młodszy developer.