Wzorzec ograniczania szybkości

Azure Service Bus
Azure Queue Storage
Azure Event Hubs

Wiele usług używa wzorca ograniczania przepustowości, aby kontrolować używane zasoby, nakładając limity szybkości, z jaką inne aplikacje lub usługi mogą uzyskiwać do nich dostęp. Możesz użyć wzorca ograniczania szybkości, aby uniknąć lub zminimalizować błędy ograniczania przepustowości związane z tymi limitami ograniczania przepustowości i ułatwić dokładniejsze przewidywanie przepływności.

Wzorzec ograniczania szybkości jest odpowiedni w wielu scenariuszach, ale jest szczególnie przydatny w przypadku powtarzających się zadań automatycznych na dużą skalę, takich jak przetwarzanie wsadowe.

Kontekst i problem

Wykonywanie dużej liczby operacji przy użyciu usługi ograniczonej może spowodować zwiększenie ruchu i przepływności, ponieważ konieczne będzie śledzenie odrzuconych żądań, a następnie ponowienie próby wykonania tych operacji. W miarę wzrostu liczby operacji limit ograniczania przepustowości może wymagać wielu przebiegów ponownego uwierzytelniania danych, co powoduje większy wpływ na wydajność.

Rozważmy na przykład następujące naiwne ponawianie próby w procesie błędu w celu pozyskiwania danych do usługi Azure Cosmos DB:

  1. Aplikacja musi pozyskiwać 10 000 rekordów do usługi Azure Cosmos DB. Każdy rekord kosztuje 10 jednostek żądania (RU) do pozyskiwania, co wymaga łącznie 100 000 jednostek RU do ukończenia zadania.
  2. Wystąpienie usługi Azure Cosmos DB ma aprowizowaną pojemność 20 000 jednostek RU.
  3. Wszystkie 10 000 rekordów jest wysyłanych do usługi Azure Cosmos DB. 2000 rekordów zostało zapisanych pomyślnie, a 8000 rekordów zostało odrzuconych.
  4. Do usługi Azure Cosmos DB wysyłasz pozostałe 8000 rekordów. 2000 rekordów zostało zapisanych pomyślnie, a 6000 rekordów zostało odrzuconych.
  5. Do usługi Azure Cosmos DB wysyłasz pozostałe 6000 rekordów. 2000 rekordów zostało zapisanych pomyślnie, a 4000 rekordów zostało odrzuconych.
  6. Do usługi Azure Cosmos DB wysyłasz pozostałe 4000 rekordów. 2000 rekordów zostało zapisanych pomyślnie, a 2000 rekordów zostało odrzuconych.
  7. Do usługi Azure Cosmos DB wysyłasz pozostałe 2000 rekordów. Wszystkie są zapisywane pomyślnie.

Zadanie pozyskiwania zostało ukończone pomyślnie, ale dopiero po wysłaniu 30 000 rekordów do usługi Azure Cosmos DB, mimo że cały zestaw danych składał się tylko z 10 000 rekordów.

W powyższym przykładzie należy wziąć pod uwagę dodatkowe czynniki:

  • Duża liczba błędów może również spowodować dodatkową pracę w celu zarejestrowania tych błędów i przetworzenia wynikowych danych dziennika. Takie naiwne podejście obsłuży 20 000 błędów, a rejestrowanie tych błędów może narzucić koszt przetwarzania, pamięci lub zasobu magazynu.
  • Nie znając limitów ograniczania przepływności usługi pozyskiwania, naiwne podejście nie ma sposobu na określenie oczekiwań dotyczących czasu przetwarzania danych. Ograniczanie szybkości umożliwia obliczenie czasu wymaganego do pozyskiwania.

Rozwiązanie

Ograniczanie szybkości może zmniejszyć ruch i potencjalnie zwiększyć przepływność, zmniejszając liczbę rekordów wysyłanych do usługi w danym okresie.

Usługa może ograniczać przepływność na podstawie różnych metryk w czasie, takich jak:

  • Liczba operacji (na przykład 20 żądań na sekundę).
  • Ilość danych (na przykład 2 GiB na minutę).
  • Względny koszt operacji (na przykład 20 000 jednostek RU na sekundę).

Niezależnie od metryki używanej do ograniczania szybkości implementacja ograniczania szybkości będzie obejmować kontrolowanie liczby i/lub rozmiaru operacji wysyłanych do usługi w określonym przedziale czasu, optymalizując użycie usługi, nie przekraczając jej pojemności ograniczania.

W scenariuszach, w których interfejsy API mogą obsługiwać żądania szybciej niż wszystkie dozwolone usługi pozyskiwania z mniejszą przepustowością, musisz zarządzać szybkością korzystania z usługi. Jednak tylko traktowanie ograniczania jako problemu z niezgodnością szybkości danych i po prostu buforowanie żądań pozyskiwania, dopóki usługa ograniczona nie będzie mogła nadrobić zaległości, jest ryzykowna. Jeśli aplikacja ulegnie awarii w tym scenariuszu, ryzyko utraty dowolnego z tych buforowanych danych.

Aby uniknąć tego ryzyka, rozważ wysłanie rekordów do trwałego systemu obsługi komunikatów, który może obsługiwać pełną szybkość pozyskiwania. (Usługi takie jak Azure Event Hubs mogą obsługiwać miliony operacji na sekundę). Następnie można użyć jednego lub większej liczby procesorów zadań, aby odczytać rekordy z systemu obsługi komunikatów z kontrolowaną szybkością, która mieści się w granicach usługi z ograniczeniami. Przesyłanie rekordów do systemu obsługi komunikatów może zapisywać pamięć wewnętrzną, umożliwiając oddzielenie tylko rekordów, które mogą być przetwarzane w danym interwale czasu.

Platforma Azure udostępnia kilka trwałych usług obsługi komunikatów, których można używać z tym wzorcem, w tym:

Trwały przepływ obsługi komunikatów z trzema procesorami zadań wywołującymi usługę ograniczoną.

Podczas wysyłania rekordów okres używany do wydawania rekordów może być bardziej szczegółowy niż okres ograniczania usługi. Systemy często ustawiają ograniczenia na podstawie przedziałów czasu, z którym można łatwo zrozumieć i pracować z nimi. Jednak w przypadku komputera z uruchomioną usługą te przedziały czasowe mogą być bardzo długie w porównaniu z szybkością przetwarzania informacji. Na przykład system może ograniczać przepustowość na sekundę lub minutę, ale często kod przetwarza kolejność nanosekund lub milisekund.

Chociaż nie jest to wymagane, często zaleca się wysyłanie mniejszych ilości rekordów w celu zwiększenia przepływności. Dlatego zamiast próbować wsadować rzeczy do wydania raz na sekundę lub raz na minutę, możesz być bardziej szczegółowe niż to, aby zachować zużycie zasobów (pamięć, procesor, sieć itd.) przepływając z większą szybkością, zapobiegając potencjalnym wąskim gardłom z powodu nagłych wzrostów żądań. Jeśli na przykład usługa zezwala na 100 operacji na sekundę, implementacja ogranicznika szybkości może nawet przekraczać liczbę żądań, zwalniając 20 operacji co 200 milisekund, jak pokazano na poniższym wykresie.

Wykres przedstawiający ograniczanie szybkości w czasie.

Ponadto czasami jest konieczne, aby wiele niezordowanych procesów współdzieliło usługę z wyjątkiem ograniczenia. Aby zaimplementować ograniczanie szybkości w tym scenariuszu, można logicznie podzielić pojemność usługi, a następnie użyć rozproszonego systemu wzajemnego wykluczania do zarządzania blokadami wyłączności na tych partycjach. Niezrównoważowane procesy mogą następnie konkurować o blokady na tych partycjach, gdy potrzebują pojemności. Dla każdej partycji, dla której proces przechowuje blokadę, jest przyznawana pewna ilość pojemności.

Jeśli na przykład system ograniczony zezwala na 500 żądań na sekundę, możesz utworzyć 20 partycji o wartości 25 żądań na sekundę. Jeśli proces potrzebny do wystawienia 100 żądań, może zostać wyświetlony monit o rozproszony system wzajemnego wykluczania dla czterech partycji. System może udzielić dwóch partycji przez 10 sekund. Następnie proces ogranicza liczbę żądań do 50 żądań na sekundę, wykonuje zadanie w ciągu dwóch sekund, a następnie zwalnia blokadę.

Jednym ze sposobów zaimplementowania tego wzorca jest użycie usługi Azure Storage. W tym scenariuszu utworzysz jeden 0-bajtowy obiekt blob na partycję logiczną w kontenerze. Aplikacje mogą następnie uzyskiwać dzierżawy na wyłączność bezpośrednio względem tych obiektów blob przez krótki czas (na przykład 15 sekund). Dla każdej dzierżawy udzielono aplikacji, będzie ona mogła korzystać z pojemności tej partycji. Następnie aplikacja musi śledzić czas dzierżawy, aby po wygaśnięciu mogła przestać korzystać z przyznanej pojemności. Podczas implementowania tego wzorca często chcesz, aby każdy proces próbował dzierżawić losową partycję, gdy potrzebuje pojemności.

Aby jeszcze bardziej zmniejszyć opóźnienie, możesz przydzielić niewielką ilość wyłącznej pojemności dla każdego procesu. Następnie proces będzie dążył tylko do uzyskania dzierżawy pojemności udostępnionej, jeśli będzie konieczne przekroczenie jej pojemności zarezerwowanej.

Partycje obiektów blob platformy Azure

Alternatywą dla usługi Azure Storage może być również zaimplementowanie tego rodzaju systemu zarządzania dzierżawami przy użyciu takich technologii jak Zookeeper, Consul itp., Redis/Redsync i innych.

Problemy i kwestie do rozważenia

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

  • Chociaż wzorzec ograniczania szybkości może zmniejszyć liczbę błędów ograniczania przepustowości, aplikacja nadal będzie musiała prawidłowo obsługiwać wszelkie błędy ograniczania przepustowości, które mogą wystąpić.
  • Jeśli aplikacja ma wiele strumieni roboczych, które uzyskują dostęp do tej samej usługi z ograniczeniami, należy zintegrować wszystkie z nich ze strategią ograniczania szybkości. Na przykład można obsługiwać zbiorcze ładowanie rekordów do bazy danych, ale także wykonywanie zapytań dotyczących rekordów w tej samej bazie danych. Możesz zarządzać pojemnością, zapewniając, że wszystkie strumienie robocze są bramowane za pomocą tego samego mechanizmu ograniczania szybkości. Alternatywnie można zarezerwować oddzielne pule pojemności dla każdego strumienia roboczego.
  • Usługa ograniczona może być używana w wielu aplikacjach. W niektórych — ale nie wszystkich — przypadkach można koordynować to użycie (jak pokazano powyżej). Jeśli zaczniesz widzieć większą niż oczekiwaną liczbę błędów ograniczania przepustowości, może to być oznaką rywalizacji między aplikacjami, które uzyskują dostęp do usługi. Jeśli tak, może być konieczne tymczasowe zmniejszenie przepływności nałożonej przez mechanizm ograniczania szybkości do momentu obniżenia użycia z innych aplikacji.

Kiedy używać tego wzorca

Użyj tego wzorca, aby:

  • Zmniejsz liczbę błędów ograniczania przepustowości zgłaszanych przez usługę ograniczoną przez ograniczanie przepustowości.
  • Zmniejsz ruch w porównaniu do naiwnego ponawiania próby w przypadku podejścia do błędu.
  • Zmniejsz zużycie pamięci przez oddzielenie rekordów od kolejkowania tylko wtedy, gdy istnieje pojemność do ich przetworzenia.

Projekt obciążenia

Architekt powinien ocenić, w jaki sposób wzorzec ograniczania szybkości może być używany w projekcie obciążenia, aby sprostać celom i zasadom opisanym w filarach platformy Azure Well-Architected Framework. Na przykład:

Filar Jak ten wzorzec obsługuje cele filaru
Decyzje projektowe dotyczące niezawodności pomagają obciążeniu stać się odporne na awarię i zapewnić, że zostanie przywrócony do w pełni funkcjonalnego stanu po wystąpieniu awarii. Ta taktyka chroni klienta, uznając i przestrzegając ograniczeń i kosztów komunikacji z usługą, gdy usługa chce uniknąć nadmiernego użycia.

- RE:07 Self-preservation

Podobnie jak w przypadku każdej decyzji projektowej, należy rozważyć wszelkie kompromisy w stosunku do celów innych filarów, które mogą zostać wprowadzone przy użyciu tego wzorca.

Przykład

Poniższa przykładowa aplikacja umożliwia użytkownikom przesyłanie rekordów różnych typów do interfejsu API. Istnieje unikatowy procesor zadań dla każdego typu rekordu, który wykonuje następujące kroki:

  1. Walidacja
  2. Wzbogacenie
  3. Wstawianie rekordu do bazy danych

Wszystkie składniki aplikacji (interfejs API, procesor zadań A i procesor zadań B) są oddzielnymi procesami, które mogą być skalowane niezależnie. Procesy nie komunikują się bezpośrednio ze sobą.

Przepływ z wieloma kolejkami z wieloma procesorami z partycjonowaną przestrzenią dyskową na potrzeby dzierżaw, zapisu w bazie danych.

Ten diagram zawiera następujący przepływ pracy:

  1. Użytkownik przesyła do interfejsu API 10 000 rekordów typu A.
  2. Interfejs API zapisuje te 10 000 rekordów w kolejce A.
  3. Użytkownik przesyła do interfejsu API 5000 rekordów typu B.
  4. Interfejs API zapisuje te 5000 rekordów w kolejce B.
  5. Procesor zadań A widzi kolejkę A zawiera rekordy i próbuje uzyskać wyłączną dzierżawę obiektu blob 2.
  6. Procesor zadań B widzi, że kolejka B zawiera rekordy i próbuje uzyskać wyłączną dzierżawę obiektu blob 2.
  7. Procesor zadań A nie może uzyskać dzierżawy.
  8. Procesor zadań B uzyskuje dzierżawę obiektu blob 2 przez 15 sekund. Teraz może ograniczać liczbę żądań do bazy danych w tempie 100 na sekundę.
  9. Procesor zadań B usuwa 100 rekordów z kolejki B i zapisuje je.
  10. Jedna sekunda przechodzi.
  11. Procesor zadań A widzi Queue A zawiera więcej rekordów i próbuje uzyskać wyłączną dzierżawę obiektu blob 6.
  12. Procesor zadań B widzi kolejkę B zawiera więcej rekordów i próbuje uzyskać wyłączną dzierżawę obiektu blob 3.
  13. Procesor zadań A uzyskuje dzierżawę obiektu blob 6 przez 15 sekund. Teraz może ograniczać liczbę żądań do bazy danych w tempie 100 na sekundę.
  14. Procesor zadań B uzyskuje dzierżawę obiektu blob 3 przez 15 sekund. Teraz może ograniczać liczbę żądań do bazy danych w tempie 200 na sekundę. (Zawiera również dzierżawę obiektu blob 2).
  15. Procesor zadań A usuwa 100 rekordów z kolejki A i zapisuje je.
  16. Procesor zadań B dequeues 200 rekordów z kolejki B i zapisuje je.
  17. Jedna sekunda przechodzi.
  18. Procesor zadań A widzi Queue A zawiera więcej rekordów i próbuje uzyskać wyłączną dzierżawę obiektu blob 0.
  19. Procesor zadań B widzi kolejkę B zawiera więcej rekordów i próbuje uzyskać wyłączną dzierżawę obiektu blob 1.
  20. Procesor zadań A uzyskuje dzierżawę obiektu blob 0 przez 15 sekund. Teraz może ograniczać liczbę żądań do bazy danych w tempie 200 na sekundę. (Zawiera również dzierżawę obiektu blob 6).
  21. Procesor zadań B uzyskuje dzierżawę obiektu blob 1 przez 15 sekund. Teraz może ograniczać liczbę żądań do bazy danych z szybkością 300 na sekundę. (Zawiera również dzierżawę obiektów blob 2 i 3).
  22. Procesor zadań A usuwa 200 rekordów z kolejki A i zapisuje je.
  23. Procesor zadań B usuwa 300 rekordów z kolejki B i zapisuje je.
  24. I tak dalej...

Po 15 sekundach jedno lub oba zadania nadal nie zostaną ukończone. Po wygaśnięciu dzierżaw procesor powinien również zmniejszyć liczbę żądań, które usuwają z kolejki i zapisu.

Logo usługi GitHub Implementacje tego wzorca są dostępne w różnych językach programowania:

  • Implementacja języka Go jest dostępna w witrynie GitHub.
  • Implementacja języka Java jest dostępna w witrynie GitHub.

Podczas implementowania tego wzorca mogą być istotne następujące wzorce i wskazówki:

  • Ograniczanie przepustowości. Omówiony tutaj wzorzec ograniczania szybkości jest zwykle implementowany w odpowiedzi na usługę, która jest ograniczana.
  • Ponowienie próby. Gdy żądania ograniczania usługi powodują błędy ograniczania przepustowości, zazwyczaj zaleca się ponawianie prób po odpowiednim interwale.

Bilansowanie obciążenia opartego na kolejce jest podobne, ale różni się od wzorca ograniczania szybkości na kilka kluczowych sposobów:

  1. Ograniczanie szybkości nie musi używać kolejek do zarządzania obciążeniem, ale musi korzystać z trwałej usługi obsługi komunikatów. Na przykład wzorzec ograniczania szybkości może korzystać z usług, takich jak Apache Kafka lub Azure Event Hubs.
  2. Wzorzec ograniczania szybkości wprowadza koncepcję rozproszonego systemu wzajemnego wykluczania na partycjach, który umożliwia zarządzanie pojemnością dla wielu niezordowanych procesów komunikujących się z tą samą usługą ograniczaną.
  3. Wzorzec bilansowania obciążenia opartego na kolejce ma zastosowanie w dowolnym momencie, gdy między usługami występuje niezgodność wydajności lub poprawa odporności. Dzięki temu jest to szerszy wzorzec niż ograniczanie szybkości, co jest bardziej szczegółowo związane z wydajnym uzyskiwaniem dostępu do usługi z ograniczeniami.