Wyjaśnienie ograniczenia szybkości interfejsu API: algorytmy, nagłówki i obsługa błędów 429

  1. Strona główna
  2. Blog
  3. Rozwój oprogramowania

Rozwój oprogramowania

Wyjaśnienie ograniczenia szybkości interfejsu API: Algorytmy, nagłówki i obsługa błędów 429

Limity szybkości chronią interfejsy API przed przeciążeniem i nadużyciami. Dowiedz się, jak zachowują się główne algorytmy, jak wygląda dobra odpowiedź 429 i jak klienci powinni się wycofać, korzystając z przeprowadzonej przez nas symulacji.

8 min czytania
Wyjaśnienie ograniczania szybkości API: algorytmy, nagłówki i obsługa błędów 429

Najważniejsze

  • Ograniczenie szybkości ogranicza liczbę żądań, które klient może wykonać w danym okresie.
  • zasobnik tokenów umożliwia krótkie serie przy jednoczesnym wymuszaniu średniej szybkości.
  • Proste stałe okno może przekroczyć dwukrotnie większy limit na granicy okna.
  • Zwróć 429 zbyt wielu żądań z nagłówkiem Retry-Aft i poproś klientów o wycofywanie się z powodu fluktuacji.

Jeden błędny skrypt wywołuje Twój interfejs API w ścisłej pętli. Bez ograniczeń może spowolnić usługę każdego innego użytkownika lub spowodować duży rachunek za chmurę. Ograniczenie szybkości interfejsu API to bariera chroniąca pojedynczego klienta przed przejęciem więcej niż mu się należy.

Aby pokazać, jak zachowują się popularne algorytmy, napisaliśmy małe wersje Pythona i podaliśmy im symulowane czasy żądań. Poniższe dane wyjściowe pochodzą z tego przebiegu. Uczą modeli, a nie kodu produkcyjnego, a prawdziwe bramy mają więcej funkcji.

Co to jest ograniczenie szybkości?

Ograniczanie szybkości zlicza żądania na klienta i odrzuca lub opóźnia żądania powyżej uzgodnionego limitu, np. 100 żądań na minutę. Powiązany pomysł, dławienie, często oznacza spowalnianie żądań, a nie ich odrzucanie. limit to zazwyczaj większy limit na dłuższy okres, np. 10 000 połączeń miesięcznie w ramach planu cenowego.

Dlaczego interfejsy API tego potrzebują

  • Stabilność: skoki ruchu nie mogą przeciążyć baz danych ani usług podrzędnych.
  • Sprawiedliwość: jeden ciężki klient nie może głodzić pozostałych.
  • Bezpieczeństwo: limity spowalniają próby logowania metodą brute-force i skrobanie.
  • Kontrola kosztów: drogie operacje, takie jak wywoływanie modeli AI, mieszczą się w budżecie.

Ograniczanie szybkości jest podstawową kontrolą w Top 10 zabezpieczeń API OWASP, w ramach nieograniczonego zużycia zasobów. W naszym przewodniku testowanie API za pomocą Postmana pokazano, jak wysyłać powtarzające się żądania potrzebne do sprawdzenia.

Wiaderko na żetony

Wyobraź sobie wiadro mieszczące do 5 żetonów. Każde żądanie wymaga tokenu. Tokeny uzupełniają się z częstotliwością 1 na sekundę, a wiadro nigdy nie mieści więcej niż 5. Jeśli wiadro jest puste, żądanie zostaje odrzucone.

class TokenBucket:
    def __init__(self, capacity, refill_per_sec):
        self.capacity = capacity
        self.rate = refill_per_sec
        self.tokens = capacity
        self.last = 0.0

    def allow(self, now):
        elapsed = now - self.last
        self.tokens = min(self.capacity, self.tokens + elapsed * self.rate)
        self.last = now
        if self.tokens >= 1:
            self.tokens -= 1
            return True
        return False

Wysłaliśmy 7 próśb na raz, później więcej:

burst of 7 at t=0:  [True, True, True, True, True, False, False]
t=2.0:              [True, True, False]
t=10:               5 allowed of 10

Pierwsze 5 przeszło w formie serii, potem wiadro było puste. Dwie sekundy później 2 tokeny zostały uzupełnione, więc poszły kolejne 2 żądania. Po długiej przerwie wiadro znów było pełne, ale ograniczone do 5. To zachowanie polegające na przerywaniu, a następnie stałym odpowiada rzeczywistemu ruchowi i dlatego zasobniki tokenów są powszechne w bramach API. nieszczelny wiadro jest bliskim krewnym, który przetwarza żądania ze stałą szybkością, wygładzając dane wyjściowe, zamiast pozwalać na serie.

Okno stałe vs okno przesuwne

Licznik stałego okna to najprostsze podejście: zezwalaj na 10 żądań na minutę i resetuj licznik, gdy zmieni się minuta. Ma znaną wadę na granicy. Wysłaliśmy 10 żądań po 59,9 sekundy i 10 kolejnych po 60,0 sekundach, z limitem 10 w oknie 60-sekundowym:

fixed window allowed around boundary: 20 of 20
sliding log allowed around boundary:  10 of 20

Stałe okno umożliwiło wykonanie wszystkich 20 żądań w ciągu jednej dziesiątej sekundy, czyli dwukrotnie szybciej niż zamierzono, ponieważ wpadały one do dwóch różnych okien. Dziennik przesuwny zapamiętuje czas każdego ostatniego żądania i zlicza te z ostatnich 60 sekund, więc poprawnie dozwolone było tylko 10.

Kompromisem jest pamięć. Przesuwany dziennik przechowuje znacznik czasu na każde żądanie. Lada z przesuwanym oknem to popularne rozwiązanie pośrednie. Szacuje szybkość, łącząc liczbę bieżących i poprzednich okien, używając niewielkiej ilości pamięci z dobrą dokładnością.

Porównanie algorytmów

Algorytm Wybuchy Dokładność Pamięć
Naprawiono okno Do 2x na granicach Niski Bardzo niski
Przesuwany dziennik Nie Dokładne Wysoka
Licznik z przesuwanym oknem Mały Dobrze Niski
Wiaderko na żetony Dozwolone do limitu pojemności Dobrze Niski
Nieszczelne wiadro Wygładzone Dobrze Niski

Czym się ograniczać

  • Klucz API lub identyfikator użytkownika: najuczciwsza opcja w przypadku uwierzytelnionych interfejsów API.
  • Adres IP: przydatny w przypadku publicznych punktów końcowych, ale wielu użytkowników może dzielić jeden adres IP, a osoby atakujące mogą je zmieniać.
  • Punkt końcowy: drogie operacje, takie jak wyszukiwanie, eksport czy logowanie, zasługują na bardziej rygorystyczne limity.
  • Poziom planu: klienci bezpłatni i płatni często otrzymują różne limity.

Punkty końcowe logowania wymagają szczególnej opieki. Ogranicz zarówno według konta, jak i adresu IP, więc atakujący nie mogą po prostu zgadywać na wiele adresów IP lub wielu kont.

Odpowiedź 429

Gdy klient przekroczy limit, zwróć HTTP 429 Too Many Requests, a nie 500 czy 403. Dołącz nagłówki informujące klienta, co się stało i kiedy spróbować ponownie. Nasza symulacja wyprodukowała ten zestaw dla klienta, który nie miał już żadnych żądań:

HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30
Retry-After: 30

Retry-After to standardowy nagłówek HTTP. Nagłówki RateLimit-* pochodzą z wersji roboczej IETF, a wiele interfejsów API korzysta z własnych X-RateLimit-* wariantów. Cokolwiek wybierzesz, udokumentuj to. Więcej informacji na temat kodów stanu znajdziesz w naszej ściągawce dotyczącej kodów stanu HTTP.

Obsługa limitów jako klient

Ponowna próba natychmiast po 429 pogarsza sytuację. Jeśli tysiące klientów zrobi to w tym samym momencie, trafiają na serwer zsynchronizowanymi falami. Rozwiązaniem jest wykładnicze wycofywanie się z jitterem: poczekaj dłużej po każdej awarii i dodaj losowość.

def backoff(attempt, base=0.5, cap=30, rng=random.Random(4)):
    return round(min(cap, base * 2 ** attempt) * rng.random(), 2)

[backoff(a) for a in range(6)]
# [0.12, 0.1, 0.79, 0.62, 0.53, 6.43]

Jest to „pełny jitter”: losowe oczekiwanie od zera do wykładniczo rosnącego pułapu. Oczekiwania wyglądają nieregularnie i o to właśnie chodzi. Klienci rozproszyli się, zamiast ponawiać próby razem. Uruchomiliśmy generator losowy, aby można było odtworzyć dane wyjściowe.

Lista kontrolna klienta: honoruj ​​Retry-After, jeśli jest obecny, ograniczaj liczbę ponownych prób, tylko żądania ponawiania prób, które można bezpiecznie powtórzyć, i obserwuj nagłówek pozostałych żądań, aby zwolnić, zanim osiągniesz limit.

Rozproszone ograniczanie szybkości

Dzięki kilku serwerom API za modułem równoważenia obciążenia każdy serwer liczący samodzielnie pozwala klientowi przekroczyć limit, rozkładając żądania między sobą. Zespoły zwykle przechowują liczniki we współdzielonym sklepie, takim jak Redis, z operacjami atomowymi lub krótkim skryptem, dzięki czemu dwa serwery nie mogą przyjąć ostatniego tokenu. Bramy takie jak Kong, NGINX, Envoy i bramy Cloud API zapewniają to jako konfigurację. W przypadku tego artykułu nie uruchomiliśmy instalacji rozproszonej.

Testowanie limitów szybkości

  • Wysyłaj żądania do limitu i potwierdź ich powodzenie.
  • Wyślij jeszcze jeden i potwierdź kod 429 z poprawnymi nagłówkami.
  • Poczekaj na czas resetowania i potwierdź, że żądania ponownie się powiodły.
  • Sprawdź limity na klucz: użycie jednego klienta nie może blokować innego.
  • Przetestuj granicę między oknami pod kątem problemu podwójnego impulsu.
  • Użyj narzędzia do ładowania, zgodnie z opisem w testowaniu obciążenia a testowaniu warunków skrajnych, aby sprawdzić zachowanie w przypadku dużej współbieżności.

W testach jednostkowych wstrzyknij bieżący czas, tak jak robi to nasza symulacja w przypadku now, aby testy nigdy nie musiały spać.

Projektowanie granic, z którymi użytkownicy mogą żyć

Wybór liczb ma takie samo znaczenie jak wybór algorytmu. Zacznij od prawdziwego ruchu: sprawdź, ile żądań wykonują Twoi najwięksi legalni klienci w ciągu minuty i wygodnie ustal limity powyżej tej wartości. Limit blokujący normalne użytkowanie tworzy zgłoszenia do pomocy technicznej, a nie bezpieczeństwo.

  • Opublikuj limity w dokumentacji API dla każdego punktu końcowego i planu.
  • Ostrzegaj przed zablokowaniem. Nagłówki pokazujące pozostałe żądania pozwalają dobrze zachowującym się klientom na samodzielne spowolnienie.
  • Oferuj alternatywy dla intensywnego odpytywania, takie jak webhooki, masowe punkty końcowe lub większe strony.
  • Monitoruj stawki 429. Nagły wzrost może oznaczać atak, wydanie klienta z błędami lub limity, które są teraz zbyt wąskie.
  • Celowo przyznaj wyjątki, na przykład podniesione limity dla partnera, zapisane w konfiguracji, a nie zakodowane na stałe.

Zdecyduj także, co stanie się z żądaniami, które dotrą nieco powyżej limitu. Niektóre interfejsy API umieszczają w kolejce niewielką liczbę dodatkowych żądań zamiast je odrzucać, co łagodzi krótkie skoki. Inni dają pierwszeństwo istotnym punktom końcowym, takim jak płatności, przed mniej krytycznymi, takimi jak analityka, tak aby ważny ruch nadal płynął, gdy system jest pod presją.

Podsumowanie

Dobre ograniczanie stawek to umowa pomiędzy API a klientem. Serwer wybiera uczciwy algorytm, zwykle zasobnik tokenów lub przesuwane okno, i jasno wyjaśnia jego ograniczenia za pomocą 429 i Retry-After. Klient szanuje te sygnały i wycofuje się z jitterem. Zacznij od dużych limitów na klucz API, obserwuj rzeczywiste wykorzystanie i dokręcaj tylko tam, gdzie jest to konieczne.

Najczęstsze pytania

Co to jest ograniczenie szybkości interfejsu API?

Jest to limit liczby żądań, jakie klient może skierować do interfejsu API w danym czasie, np. 100 na minutę. Żądania przekraczające limit są odrzucane lub opóźniane.

Co oznacza HTTP 429?

429 Too Many Requests oznacza, że ​​klient przekroczył limit szybkości. Odpowiedź często zawiera nagłówek Retry-After informujący, jak długo należy czekać.

Jaki jest algorytm zasobnika tokenów?

Każde żądanie wykorzystuje token z zasobnika, który uzupełnia się ze stałą szybkością aż do maksimum. Pozwala na krótkie serie, jednocześnie wymuszając średnią szybkość.

Jaka jest różnica między ograniczaniem szybkości a ograniczaniem przepustowości?

Ograniczanie szybkości zwykle odrzuca żądania przekraczające limit. Zamiast tego ograniczanie często spowalnia je lub ustawia w kolejce. Terminy te są często używane zamiennie.

Jak klient powinien postępować z limitami stawek?

Przestrzegaj opcji Retry-After, ponawiaj próby z wykładniczym wycofywaniem i fluktuacjami, ograniczaj liczbę ponownych prób i zwalniaj, gdy liczba pozostałych żądań jest niska.

Ucz się dalej

Pracuj z Stack Quora

Potrzebujesz pomocy przy testowaniu, WordPressie lub SEO?

Pomagamy zespołom dostarczać lepsze oprogramowanie i rozwijać ich witryny. Powiedz nam, nad czym pracujesz, a my zaproponujemy jasny następny krok.

Czytaj dalej

Więcej poradników na testowanie, sztuczna inteligencja i nowoczesny rozwój

Podobał Ci się ten artykuł? Wybierz temat i przejdź bezpośrednio do naszych najnowszych tutoriali.

Sztuczna inteligencja

Wyjaśnienie halucynacji AI: dlaczego modele językowe tworzą rzeczy i jak je zmniejszyć

Wyjaśniono halucynacje AI: czym są, dlaczego powstają w modelach językowych, typowe typy, jak je zredukować za pomocą uziemień i podpowiedzi oraz pr…

Przeczytaj artykuł →
DevOps

Wyjaśnienie zmiennych środowiskowych: pliki .env, sekrety i konfiguracja wykonana prawidłowo

Zmienne środowiskowe wyjaśnione na przykładach Pythona, które przeprowadziliśmy: ich odczytywanie i ustawianie, dlaczego każda wartość jest ciągiem …

Przeczytaj artykuł →
Tworzenie oprogramowania

Wyjaśnienie ograniczenia szybkości interfejsu API: algorytmy, nagłówki i obsługa błędów 429

Ograniczanie szybkości API wyjaśnione za pomocą przeprowadzonej przez nas symulacji: zasobnik tokenów, stałe okno, przesuwane okno, 429 odpowiedzi, …

Przeczytaj artykuł →
Automatyzacja testów

Wyśmiewanie w Pythonie: unittest.mock, patch i błędy, które sprawiają, że testy kłamią

Wyśmiewanie w Pythonie za pomocą unittest.mock, przetestowane: Mock i zwracana wartość, efekt uboczny, łatanie tam, gdzie używana jest nazwa, specyf…

Przeczytaj artykuł →
Testowanie oprogramowania

Wyjaśnienie zasięgu kodu: pokrycie linii i oddziału oraz co naprawdę oznacza 100%

Wyjaśnienie pokrycia kodu za pomocą prawdziwego pliku cover.py: pokrycie linii, gałęzi i warunków, przeczytanie raportu, dlaczego 100% pokrycia może…

Przeczytaj artykuł →
Bazy danych i SQL

DELETE vs TRUNCATE vs DROP w SQL: różnice, wycofywanie i kiedy używać każdego z nich

DELETE vs TRUNCATE vs DROP wyjaśnione za pomocą przetestowanego SQL: co każdy usuwa, wycofywanie, wyzwalacze, resetowanie tożsamości, prędkość i czy…

Przeczytaj artykuł →
Bazy danych i SQL

Wyjaśnienie widoków SQL: tworzenie, używanie i unikanie pułapek wraz z przykładami

Widoki SQL wyjaśnione za pomocą zapytań, które uruchomiliśmy: tworzenie widoku, wyniki na żywo, ukrywanie kolumn, wydajność, widoki z możliwością ak…

Przeczytaj artykuł →
Tworzenie oprogramowania

Interfejs TypeScript a typ: rzeczywiste różnice, testowane za pomocą kompilatora

Interfejs TypeScript a typ, sprawdzony z prawdziwym kompilatorem: co oba mogą zrobić, co tylko jeden, łączenie deklaracji, rozszerzenia a przecięcia…

Przeczytaj artykuł →
Tworzenie oprogramowania

Python *args i **kwargs wyjaśnione: elastyczne funkcje z przejrzystymi przykładami

Python *args i **kwargs wyjaśnione za pomocą uruchomionego przez nas kodu: pakowanie i rozpakowywanie, kolejność parametrów, argumenty zawierające t…

Przeczytaj artykuł →
Tworzenie oprogramowania

Funkcje Lambda w Pythonie: składnia, rzeczywiste zastosowania i kiedy ich nie używać

Funkcje lambda w Pythonie wyjaśnione za pomocą uruchomionego przez nas kodu: składnia, sortowanie za pomocą klucza, mapa i filtr, domknięcia, pułapk…

Przeczytaj artykuł →
Sztuczna inteligencja

Kim są agenci AI? Jak one działają, dzięki prostej pętli agenta, którą możesz uruchomić

Kim są agenci AI? Prosty przewodnik po pętli agenta, narzędziach, pamięci i poręczach, z małą pętlą w Pythonie, którą uruchomiliśmy, prawdziwymi zag…

Przeczytaj artykuł →
DevOps

Monitorowanie a obserwowalność: prawdziwa różnica z logami, wskaźnikami i śladami

Wyjaśnienie monitorowania a obserwowalności: znane i nieznane awarie, dzienniki, metryki i ślady, opis incydentu przy kasie, złote sygnały, OpenTele…

Przeczytaj artykuł →
DevOps

Woluminy platformy Docker a mocowania powiązań: utrwalanie danych kontenera we właściwy sposób

Wyjaśniono woluminy Dockera a montowanie powiązań: dlaczego dane kontenera znikają, nazwane woluminy, montowanie powiązań, tmpfs, przykłady tworzeni…

Przeczytaj artykuł →
Automatyzacja testów

TDD vs BDD: kluczowe różnice, jedna funkcja zbudowana w obie strony i kiedy używać każdej z nich

TDD vs BDD wyjaśnione za pomocą jednej funkcji rabatowej zbudowanej w obie strony: refaktoryzacja czerwono-zielona z uruchomionym przez nas kodem, s…

Przeczytaj artykuł →
Tworzenie oprogramowania

Git merge vs rebase: różnice, przykłady i kiedy czego używać

Git merge vs rebase wyjaśnione na prawdziwym wyniku logu, z tabelą porównawczą, złotą zasadą rebase i bezpiecznym procesem pracy zespołu.

Przeczytaj artykuł →
Tworzenie oprogramowania

Python list comprehension: składnia, przykłady i kiedy jej unikać

Python list comprehension wyjaśnione: składnia, filtrowanie, if-else, zagnieżdżone pętle, zmierzone czasy i sytuacje, w których zwykła pętla for jes…

Przeczytaj artykuł →
Bazy danych i SQL

SQL JOIN – wyjaśnienie z przykładami (Inner, Left, Right, Full)

Zrozum złączenia SQL krok po kroku na przykładowych tabelach i zapytaniach: INNER, LEFT, RIGHT, FULL, CROSS i SELF, plus typowe błędy i wskazówki wy…

Przeczytaj artykuł →