Wzorzec ograniczania wydajności

Ogranicz zasoby, z których może korzystać instancja aplikacji, pojedynczy dzierżawca lub cała usługa. Dzięki temu system może działać i spełniać cele poziomu świadczenia usług (SLO) przy nagłym lub długotrwałym obciążeniu.

Kontekst i problem

Obciążenie aplikacji w chmurze różni się w czasie w zależności od aktywnych użytkowników i ich aktywności. Więcej użytkowników loguje się w godzinach pracy, a system przeprowadza zasobożerne analizy pod koniec każdego miesiąca. Występują również nagłe wybuchy. Jeśli zapotrzebowanie na przetwarzanie przekracza dostępną pojemność, system spowalnia lub kończy się niepowodzeniem. Gdy system ma uzgodniony poziom świadczenia usług, taka awaria narusza SLO.

Kilka strategii obsługuje różne obciążenia, w zależności od celów biznesowych aplikacji. Jedną ze strategii jest skalowanie automatyczne, które dopasowuje aprowizowane zasoby do bieżącego zapotrzebowania i kontroluje koszt. Jednak udostępnianie nowych zasobów zajmuje czas i zwiększa koszty. Zapotrzebowanie, które przekracza wzrost pojemności lub budżet, powoduje deficyt zasobów.

Rozwiązanie

Alternatywą dla skalowania automatycznego jest ograniczenie użycia zasobów i ograniczanie żądań, gdy użycie przekracza ten limit. Obciążenie robocze monitoruje wykorzystanie własnych zasobów i ogranicza żądania od jednego lub większej liczby użytkowników, gdy wykorzystanie przekroczy próg. Ten system nadal działa i spełnia swoje cele SLO.

Ograniczanie przepustowości jest pętlą sterowania, a nie pojedynczą decyzją przyjęcia. System potrzebuje sygnałów o małych opóźnieniach w trzech warstwach: wykorzystanie infrastruktury, stan aplikacji i liczniki dla poszczególnych podmiotów zabezpieczeń. Stale mierzy nasycenie, wymusza limity w dobrze zdefiniowanych granicach i dostosowuje te limity w miarę zmiany wzorców ruchu. Przeciążenie to normalny stan pracy, który dojrzały system potrafi wykryć i po którym potrafi wrócić do normalnego działania. Dławienie zapewnia możliwości samoochrony w ramach Twojego obciążenia roboczego.

System może wdrażać kilka strategii ograniczania przepustowości lub powiązanych strategii:

  • Limity liczby żądań na użytkownika: Odrzuca żądania użytkownika, który przekroczył już skonfigurowany limit w określonym przedziale czasowym. Ta strategia wymaga, aby system przypisywał każde żądanie do podmiotu zabezpieczeń i mierzył zużycie zasobów względem tego podmiotu zabezpieczeń. W przypadku obciążeń wielodzierżawnych zobacz Mierzenie zużycia poszczególnych dzierżawców.

  • Stopniowe ograniczanie funkcjonalności: Wyłącz lub ogranicz działanie mniej istotnych funkcji, aby kluczowe funkcje miały wystarczające zasoby. Ta strategia wymienia kompletność odpowiedzi na dostępność. Na przykład aplikacja do przesyłania strumieniowego wideo może spaść do niższej rozdzielczości.

  • Wyrównywanie obciążenia: Wyrównaj wolumen działań za pomocą kolejki. W środowisku wielodzierżawowym wyrównywanie obniża wydajność dla każdego dzierżawcy. Gdy dzierżawcy mają różne umowy dotyczące poziomu usług (SLA), natychmiast przetwarzaj zadania dla kluczowych dzierżawców, a zadania o niższym priorytecie wstrzymaj, dopóki zaległości się nie zmniejszą. Zaimplementuj to podejście przy użyciu wzorca kolejki priorytetu lub przez uwidacznianie oddzielnych punktów końcowych dla każdej warstwy priorytetu.

  • Odraczanie na podstawie priorytetów: Odraczaj operacje dla aplikacji lub dzierżawców o niższym priorytecie. Wstrzymaj lub ogranicz operacje i zgłoś wyjątek informujący dzierżawcę, aby ponowił próbę później.

  • Limity szybkości ruchu wychodzącego: Ogranicz własne wywołania wychodzące, gdy zależność zewnętrzna kończy się niepowodzeniem lub zwraca błędy. Obniż liczbę aktywnych żądań, aby nie zalewać dzienników i uniknąć kosztów ponownych prób w przypadku niesprawnej zależności. Przywracanie normalnego przepływu żądań po odzyskaniu zależności. Na przykład NServiceBus implementuje tę funkcję.

Na poniższym wykresie przedstawiono użycie zasobów (kombinację pamięci, procesora CPU, przepustowości i innych czynników) w czasie dla aplikacji, która używa trzech funkcji, oznaczonych etykietami A, B i C. Funkcja jest określonym obszarem funkcjonalności, takim jak składnik wykonujący określony zestaw zadań, fragment kodu wykonujący złożone obliczenia lub element zapewniający usługę, taką jak pamięć podręczna w pamięci.

Wykres przedstawiający użycie zasobów w czasie dla aplikacji uruchamianych w imieniu trzech użytkowników.

Wykres liniowy kreśli wykorzystanie zasobów na osi y względem czasu na osi x. Trzy kolorowe linie reprezentują funkcję A, funkcję B i funkcję C, przy czym linia funkcji A znajduje się najniżej, linia funkcji B pośrodku, a linia funkcji C najwyżej. Solidna linia pozioma w górnej części wykresu oznacza maksymalną pojemność, a linia pozioma przerywana poniżej oznacza miękki limit wykorzystania zasobów. Dwa pionowe linie kreskowane oznaczają czasy T1 i T2. Przed T1 wszystkie trzy wykresy cech wahają się, a wykres cechy C rośnie i przekracza miękki limit. W T1 wiersz funkcji B spada do zera i pozostaje na zero do T2, ponieważ funkcja B jest zawieszona w celu zwolnienia zasobów dla funkcji A i funkcji C. Linia funkcji C spada poniżej miękkiego limitu między T1 i T2, podczas gdy funkcja A jest kontynuowana normalnie. W T2 funkcja B wznawia działanie, a wszystkie trzy wiersze nadal wahają się poniżej limitu miękkiego.

Wykres jest skumulowanym wykresem warstwowym. W obszarze poniżej wiersza funkcji A przedstawiono zasoby używane przez funkcję A, obszar między wierszami funkcji A i funkcją B pokazuje zasoby używane przez funkcję B, a obszar między wierszami funkcji B i Feature C pokazuje zasoby używane przez funkcję C. Linia funkcji C znajduje się u góry stosu, dlatego pokazuje również całkowite użycie zasobów systemowych w czasie.

Wykres pokazuje stopniową degradację funkcjonalności. Tuż przed czasem T1 łączna ilość zasobów zbliża się do progu i ryzyka wyczerpania dostępnej pojemności. Funkcja B jest mniej krytyczna niż funkcja A lub Feature C, dlatego system wyłącza funkcję B i zwalnia jej zasoby. Między chwilami T1 i T2 funkcja A i funkcja C działają normalnie. Do momentu T2 całkowite wykorzystanie zasobów spada na tyle, aby ponownie włączyć funkcję B.

Możesz połączyć automatyczne skalowanie, kontrolowaną degradację i dławienie, aby aplikacje zachowywały responsywność i spełniały wymagania umów SLA. Gdy oczekujesz, że obciążenie pozostanie wysokie, dławienie utrzymuje stabilność podczas skalowania poziomego systemu. Po zakończeniu skalowania system przywraca pełną funkcjonalność.

Kolejny wykres przedstawia całkowite wykorzystanie zasobów w czasie oraz to, jak dławienie współdziała z autoskalowaniem i innymi mechanizmami kompensacyjnymi.

Wykres pokazujący skutki połączenia ograniczania przepustowości z autoskalowaniem.

Wykres liniowy kresuje wykorzystanie zasobów dla wszystkich aplikacji na osi y względem czasu na osi x. Dwa poziome linie odniesienia oznaczają miękki limit wykorzystania zasobów i maksymalną pojemność przed skalowaniem automatycznym. Wyższa linia pozioma rozpoczynająca się w czasie T2 oznacza maksymalną pojemność po autoskalowaniu. Linia wykorzystania wzrasta i zmienia się wraz z upływem czasu. Przekracza miękki limit w czasie T1, który jest punktem, w którym rozpoczyna się skalowanie automatyczne. Pomiędzy T1 a T2 system działa z ograniczoną przepustowością podczas automatycznego skalowania, a wykorzystanie zasobów pozostaje poniżej maksymalnej wydajności sprzed automatycznego skalowania. W chwili T2 kończy się automatyczne skalowanie, ograniczanie przepustowości zostaje złagodzone, a linia wykorzystania gwałtownie wzrasta i nadal waha się poniżej nowej, wyższej maksymalnej wydajności.

W chwili T1 system osiąga limit miękki i rozpoczyna skalowanie poziome. Jeśli nowe zasoby nie zostaną dostarczone na czas, zapotrzebowanie może wyczerpać istniejące zasoby i system może ulec awarii. Mechanizm ograniczania odrzuca nadmiarowe żądania w trakcie skalowania w poziomie, aby utrzymać wykorzystanie zasobów poniżej twardego limitu, a następnie znosi te ograniczenia, gdy nowa przepustowość staje się dostępna.

Wskazówka

Kontrolki brzegowe i wzorzec ograniczania przepustowości dotyczą różnych problemów. Kontrolki brzegowe, takie jak Azure DDoS Protection i reguły ograniczania szybkości zapory aplikacji internetowej (WAF), są uruchamiane na granicy sieci i porzucają woluminowy lub złośliwy ruch, zanim dotrze do aplikacji. Wzorzec dławienia działa wewnątrz aplikacji i reguluje uzasadniony ruch zgodnie z limitami zdefiniowanymi przez aplikację. Użyj obu warstw razem. Ochrona przed atakami DDoS nie powstrzyma uprawnionego użytkownika przed przeciążeniem usługi, a ograniczanie przepustowości na poziomie aplikacji nie złagodzi ataku wolumetrycznego.

Problemy i zagadnienia

Podczas podejmowania decyzji o zaimplementowaniu tego wzorca należy wziąć pod uwagę następujące kwestie:

  • Podejmij decyzje dotyczące ograniczania przepustowości na wczesnym etapie. Ograniczanie przepustowości to decyzja architektoniczna, która ma wpływ na cały system. Modernizacja jest później kosztowna.

  • Dostosuj limity dławienia do komponentu, który jako pierwszy osiąga nasycenie.

    Częstotliwość żądań jest najbardziej oczywistym parametrem do ograniczania, ale rzeczywistym wąskim gardłem często są liczba równocześnie przetwarzanych żądań, głębokość kolejki, wykorzystanie CPU lub pamięci albo własne limity zależności podrzędnej. Ograniczenie liczby żądań na sekundę nie chroni systemu, którego wąskim gardłem jest współbieżność w punkcie rozgałęzienia.

    Na każdym progu egzekwowania ograniczania przepustowości, takim jak brama, usługa, partycja lub zależność niższego poziomu, zidentyfikuj, który zasób jako pierwszy osiąga stan nasycenia, i ustaw limit dla tego wymiaru. Aby uzyskać ochronę związaną ze współbieżnością w punktach na poziomie wentylatora, zobacz wzorzec bulkhead, który uzupełnia ograniczanie przepustowości.

  • Wybierz celowo algorytm ograniczający. Dopasuj go do tolerancji składnika, który chronisz.

    Algorithm Zachowanie i optymalne dopasowanie
    Zasobnik tokenu Obsługuje wzrost do skonfigurowanego rozmiaru i wymusza stałą szybkość wypełniania. Służy do bram, które muszą absorbować krótkie skoki.
    Wyciekowy zasobnik Emituje w stałym tempie. Używaj w przypadku backendów, które wymagają stałego tempa ruchu przychodzącego.
    Okno stałe Łatwe w implementacji, ale dopuszcza następujące bezpośrednio po sobie serie na granicach okien czasowych.
    Okno przesuwane Łagodzi problem granic okna w przypadku okien stałej długości kosztem konieczności przechowywania większej ilości informacji o stanie.
  • Zdecyduj, kto ma wpływ na limit. Ograniczanie przepustowości na szeroko zdefiniowanej granicy, takiej jak brama regionalna, może wpływać na wielu niezwiązanych ze sobą użytkowników, chociaż obciążenie generuje tylko kilku z nich.

  • Zdecyduj, gdzie znajduje się licznik, gdy jeden limit obejmuje wiele węzłów. Liczniki lokalne są szybkie, ale zaniżają wynik, gdy ten sam wywołujący trafi do wielu replik. Centralny licznik we współdzielonym magazynie danych, takim jak Redis, ma wgląd we wszystkie żądania, ale wprowadza opóźnienie przy każdym podejmowaniu decyzji. Aby przybliżyć globalny limit, podziel limit na repliki i okresowo je synchronizuj.

  • Szybkie podejmowanie decyzji dotyczących ograniczania przepustowości. System musi wykrywać rosnące obciążenie, reagować i wracać do normalnego po złagodzeniu obciążenia. Proces ten wymaga ciągłego monitorowania wydajności.

  • Ograniczaj obciążenie proaktywnie, a nie dopiero na granicy awarii. Ograniczanie przepustowości, które zaczyna odrzucać żądania dopiero po przeciążeniu komponentu, powoduje gwałtowny wzrost opóźnień, zanim wywołujący odczują jakąkolwiek presję zwrotną.

    Ponieważ wykorzystanie zbliża się do twardego limitu, zacznij odrzucać rosnącą część żądań. Wczesne odrzucanie sygnalizuje wywołującym, by ograniczyli tempo żądań, i zapobiega załamaniu wydajności opóźnień, które nagłe limity często powodują. Użyj opóźnienia p99 względem SLO jako podstawowego wyzwalacza. Średnie wykorzystanie może wyglądać dobrze, podczas gdy p99 już przekroczył próg.

    Tam, gdzie można rozróżnić wartość żądań, należy najpierw odrzucać zadania o niższej wartości lub takie, które łatwiej ponowić. Aby uzyskać więcej informacji, zobacz Wzorzec kolejki priorytetu.

  • Zwróć kod stanu, który informuje klienta, gdy tymczasowe odrzucenie jest wynikiem ograniczania przepustowości:

    • HTTP 429 (zbyt wiele żądań): Obiekt wywołujący przekracza skonfigurowaną częstotliwość żądań w zdefiniowanym oknie.
    • HTTP 503 (Usługa niedostępna): Usługa nie może teraz obsłużyć żądania, często z powodu nieoczekiwanego skoku obciążenia.

    Dołącz nagłówek HTTP, Retry-After aby klient mógł wybrać strategię ponawiania prób. Zwróć wystarczająco dużo kontekstu, aby kod wywołujący mógł świadomie ponowić próbę, zamiast zgadywać. Na przykład podaj nazwę limitu, który przekracza wywołujący, wyjaśnij, którego zakresu to dotyczy, lub zasugeruj częstotliwość, która zakończy się powodzeniem. Niewyjaśnione odrzucenia nie pomagają rozmówcom dostosować się.

  • Propaguj sygnały przeciążenia od swoich zależności, zamiast je pochłaniać. Usługa, która ogranicza liczbę żądań od swoich wywołujących, musi również respektować odpowiedzi o ograniczeniu przepustowości otrzymywane od własnych zależności podrzędnych. Jeśli usługa ukrywa odpowiedź 429 lub 503 z usługi podrzędnej, po cichu ponawiając żądanie lub zwracając ogólną odpowiedź HTTP 500 (wewnętrzny błąd serwera), klienci nie mogą ograniczyć tempa żądań, liczba ponowień rośnie, a przeciążenie propaguje się z powrotem w górę łańcucha. Antywzorzec Retry Storm opisuje ten tryb awarii. Przekazuj mechanizm backpressure do wywołujących wyżej w łańcuchu, aby cały łańcuch wywołań wspólnie redukował obciążenie.

  • Spraw, by odrzucanie było tańsze niż praca, której ono zapobiega. Jeśli odmowa żądania wymaga silnego uwierzytelniania, głębokiego analizowania lub złożonej oceny zasad, fala odrzuconych żądań może nadal nasycić system. Odrzuć żądanie możliwie jak najwcześniej w potoku jego obsługi i przetestuj obciążeniowo samą ścieżkę odrzucenia.

  • Zaplanuj przypadki, w których ograniczanie przepustowości nie może kupić wystarczająco dużo czasu na skalowanie automatyczne. Jeśli popyt rośnie szybciej, niż zostaną uruchomione nowe moce, nawet dławiony system może zawieść. Jeśli ten wynik jest niedopuszczalny, zachowaj większe rezerwy pojemności i skonfiguruj bardziej agresywne skalowanie automatyczne.

  • Nie używaj buforowania jako zamiennika ograniczania szybkości. Pamięć podręczna obniża średnie obciążenie źródła, ale nie powoduje ograniczenia szczytowego obciążenia. Każde nietrafienie pamięci podręcznej jest kierowane do serwera źródłowego, a gdy popularny klucz wygaśnie przy dużym natężeniu ruchu, wiele żądań może równocześnie próbować ponownie go zapełnić. Użyj buforowania, aby zmniejszyć typowe obciążenie, a dławienia, aby ograniczyć skutki w najgorszym przypadku. Aby uzyskać więcej informacji, zobacz wzorzec Cache-Aside.

  • Normalizuj koszty zasobów dla różnych operacji, ponieważ zwykle nie mają równych kosztów wykonywania. Na przykład limity ograniczania przepustowości mogą być wyższe dla operacji odczytu i niższe w przypadku operacji zapisu. Ignorowanie kosztów operacji może wyczerpać pojemność i utworzyć wektor ataku.

  • Umożliw zmianę konfiguracji ograniczania przepustowości w czasie działania. Gdy wystąpi nietypowe obciążenie, należy dostosować limity bez przeprowadzania wdrożenia. Wdrożenia są powolne i ryzykowne podczas zdarzenia. Wzorzec zewnętrznego magazynu konfiguracji umożliwia zewnętrzną zmianę konfiguracji w czasie wykonywania.

  • Rozważ użycie limitów adaptacyjnych zamiast limitów statycznych. Niektóre pakiety SDK do ograniczania przepustowości reagują na sygnały opóźnienia lub długości kolejki, dzięki czemu limit dostosowuje się do rzeczywistych warunków pracy komponentów. Zawsze paruj ogranicznik adaptacyjny z ustawioną wartością maksymalną.

  • Weryfikuj limity ponownie wraz ze zmianą obciążenia. Ograniczniki adaptacyjne nie są w stanie nadążać za wszystkimi rodzajami odchyleń, takimi jak zmiany SLO, zmiany wydajności zależności czy zmiany kosztu pojedynczej operacji. Zaplanuj okresowe przeglądy operatorów względem tych danych wejściowych.

Kiedy należy używać tego wzorca

Użyj tego wzorca:

  • Aby utrzymać system w ramach celów SLO.

  • Aby zapobiec monopolizowaniu zasobów aplikacji przez jednego tenanta.

  • Do obsługi nagłego wzrostu aktywności.

  • Aby ograniczyć maksymalny poziom zasobów wymagany przez system.

  • Aby ograniczyć mało istotne obliczenia w okresach wysokiej emisyjności sieci elektroenergetycznej.

Projektowanie obciążenia pracy

Oceń, jak zastosować wzorzec dławienia w projekcie obciążenia roboczego, aby uwzględnić cele i zasady omówione w filarach Azure Well-Architected Framework. Poniższa tabela zawiera wskazówki dotyczące tego, jak ten wzorzec obsługuje cele poszczególnych filarów.

Filar Jak ten wzorzec obsługuje cele filaru
Decyzje projektowe dotyczące niezawodności pomagają obciążeniom stały się odporne na awarię i zapewniają, że zostanie ono przywrócone do w pełni funkcjonalnego stanu po wystąpieniu awarii. Należy zaprojektować limity, aby zapobiec wyczerpaniu zasobów, które mogą prowadzić do awarii. Możesz również użyć tego wzorca jako mechanizmu sterującego w bezproblemowym planie degradacji.

- RE:07 Instynkt samozachowawczy
Decyzje dotyczące projektowania zabezpieczeń pomagają zapewnić poufność, integralność i dostępność danych i systemów obciążenia. Możesz zaprojektować limity, aby zapobiec wyczerpaniu zasobów, które mogą wynikać z zautomatyzowanego nadużywania systemu.

- SE:06 Kontrolki sieci
- SE:08 Wzmacnianie zabezpieczeń zasobów
Optymalizacja kosztów koncentruje się na utrzymaniu i poprawiezwrotu obciążenia z inwestycji. Wymuszone limity mogą informować o modelowaniu kosztów i mogą być bezpośrednio powiązane z modelem biznesowym aplikacji. Umożliwiają one również jasne górne granice wykorzystania, które można uwzględnić w określaniu rozmiaru zasobów.

- CO:02 Model kosztów
- CO:12 Koszty skalowania
Efektywność wydajności pomaga wydajnie sprostać wymaganiom dzięki optymalizacjom skalowania, danych i kodu. Gdy system jest pod dużym obciążeniem, ten wzorzec pomaga ograniczyć przeciążenie, które może prowadzić do wąskich gardeł wydajności. Można go również używać do proaktywnego unikania problemów z hałaśliwym sąsiadem.

- PE:02 Planowanie pojemności
- PE:05 Skalowanie i partycjonowanie

Jeśli ten wzorzec wprowadza kompromisy w ramach filaru, rozważ je przed celami innych filarów.

Example

Na poniższym diagramie przedstawiono ograniczanie przepustowości w systemie wielodostępnym.

Diagram przedstawiający ograniczanie przepustowości w aplikacji wielodostępnej.

Trzej użytkownicy oznaczeni po lewej stronie reprezentują dzierżawców wielodostępnej aplikacji Surveys: Adatum, Fabrikam i Contoso. Każdy użytkownik wysyła żądania za pomocą niestandardowej domeny przypisanej do konkretnej dzierżawy, której aplikacja używa do identyfikacji dzierżawy. Adatum wysyła 5 żądań na sekundę za pośrednictwem surveys.adatum.com, firma Fabrikam wysyła 10 żądań na sekundę do surveys.fabrikam.com, a firma Contoso wysyła 150 żądań na sekundę do surveys.contoso.com. Po prawej stronie rola internetowa aplikacji ankiet mierzy liczbę żądań na sekundę dla każdego dzierżawcy. Przepływy żądań Adatum i Fabrikam przechodzą do aplikacji. Przepływ żądań Contoso jest blokowany przez błąd: odpowiedź ograniczoną z powodu limitowania, ponieważ częstotliwość żądań przekracza limit dla dzierżawy.

Użytkownicy z kilku organizacji dzierżawy uzyskują dostęp do aplikacji hostowanej w chmurze, aby wypełnić i przesłać ankiety. Aplikacja zawiera mechanizmy monitorujące częstotliwość, z jaką użytkownicy każdego dzierżawcy wysyłają żądania.

Aby uniemożliwić użytkownikom z jednej dzierżawy obniżenie czasu reakcji i dostępności dla użytkowników w innych dzierżawach, aplikacja ogranicza liczbę żądań na sekundę, którą może przesłać dowolna pojedyncza dzierżawa. Aplikacja blokuje żądania, które przekraczają ten limit.

Następny krok