Dostrajanie wydajności transakcji biznesowych rozproszonych

Azure Kubernetes Service (AKS)
Azure Cache for Redis

W tym artykule opisano sposób, w jaki zespół programistyczny używał metryk do znajdowania wąskich gardeł i poprawiania wydajności systemu rozproszonego. Artykuł jest oparty na rzeczywistym testowaniu obciążenia, które wykonaliśmy dla przykładowej aplikacji. Aplikacja pochodzi z planu bazowego Azure Kubernetes Service (AKS) dla mikrousług.

Ten artykuł jest częścią serii. Przeczytaj pierwszą część tutaj.

Scenariusz: aplikacja kliencka inicjuje transakcję biznesową, która obejmuje wiele kroków.

Ten scenariusz obejmuje aplikację dostarczania dronów działającą w usłudze AKS. Klienci używają aplikacji internetowej do planowania dostaw przez drona. Każda transakcja wymaga wielu kroków wykonywanych przez oddzielne mikrousługi na zapleczu:

  • Usługa dostarczania zarządza dostawami.
  • Usługa Drone Scheduler planuje drony do odbioru.
  • Usługa Pakiet zarządza pakietami.

Istnieją dwie inne usługi: usługa pozyskiwania, która akceptuje żądania klientów i umieszcza je w kolejce do przetwarzania, oraz usługę Przepływu pracy, która koordynuje kroki w przepływie pracy.

Diagram przedstawiający rozproszony przepływ pracy

Aby uzyskać więcej informacji na temat tego scenariusza, zobacz Projektowanie architektury mikrousług.

Test 1. Punkt odniesienia

W pierwszym teście obciążeniowym zespół utworzył klaster AKS z sześciowęźle i wdrożył trzy repliki każdej mikrousługi. Test obciążeniowy był testem obciążeniowym, który rozpoczął się od dwóch symulowanych użytkowników i zwiększał do 40 symulowanych użytkowników.

Ustawienie Wartość
Węzły klastra 6
Strąków 3 na usługę

Poniższy wykres przedstawia wyniki testu obciążeniowego, jak pokazano w programie Visual Studio. Purpurowa linia generuje obciążenie użytkownika, a pomarańczowa linia kreśle łączną liczbę żądań.

Wykres wyników testu obciążeniowego programu Visual Studio

Pierwszą rzeczą do zrealizowania tego scenariusza jest to, że żądania klientów na sekundę nie są przydatną metryką wydajności. Dzieje się tak dlatego, że aplikacja przetwarza żądania asynchronicznie, więc klient od razu otrzymuje odpowiedź. Kod odpowiedzi jest zawsze HTTP 202 (zaakceptowany), co oznacza, że żądanie zostało zaakceptowane, ale przetwarzanie nie zostało ukończone.

Chcemy wiedzieć, czy zaplecze jest zgodne z szybkością żądań. Kolejka usługi Service Bus może absorbować skoki, ale jeśli zaplecze nie może obsłużyć trwałego obciążenia, przetwarzanie spadnie dalej i dalej.

Oto bardziej informacyjny wykres. Tworzy ona wykres liczby przychodzących i wychodzących komunikatów w kolejce usługi Service Bus. Komunikaty przychodzące są wyświetlane w jasnoniebieskim, a komunikaty wychodzące są wyświetlane w kolorze ciemnoniebieskim:

Wykres przychodzących i wychodzących komunikatów

Ten wykres pokazuje, że szybkość przychodzących komunikatów wzrasta, osiągając szczyt, a następnie spadając z powrotem do zera na końcu testu obciążeniowego. Ale liczba wychodzących komunikatów szczytuje się na początku testu, a następnie rzeczywiście spada. Oznacza to, że usługa Przepływu pracy, która obsługuje żądania, nie utrzymuje się. Nawet po zakończeniu testu obciążeniowego (około 9:22 na grafie) komunikaty są nadal przetwarzane, ponieważ usługa Przepływu pracy nadal opróżnia kolejkę.

Co spowalnia przetwarzanie? Pierwszą rzeczą do wyszukania są błędy lub wyjątki, które mogą wskazywać na systematyczny problem. Mapa aplikacji w usłudze Azure Monitor przedstawia wykres wywołań między składnikami i jest szybkim sposobem wykrywania problemów, a następnie kliknij, aby uzyskać więcej szczegółów.

Na pewno mapa aplikacji pokazuje, że usługa Przepływu pracy otrzymuje błędy z usługi dostarczania:

Zrzut ekranu przedstawiający mapę aplikacji

Aby wyświetlić więcej szczegółów, możesz wybrać węzeł na wykresie i kliknąć w widok transakcji kompleksowej. W takim przypadku widać, że usługa dostarczania zwraca błędy HTTP 500. Komunikaty o błędach wskazują, że wyjątek jest zgłaszany z powodu limitów pamięci w Azure Cache for Redis.

Zrzut ekranu przedstawiający widok transakcji kompleksowej

Możesz zauważyć, że te wywołania do usługi Redis nie są wyświetlane na mapie aplikacji. Dzieje się tak, ponieważ biblioteka .NET dla usługi Application Insights nie obsługuje wbudowanej obsługi śledzenia usługi Redis jako zależności. (Aby zapoznać się z listą obsługiwanych elementów, zobacz Automatyczne zbieranie zależności). Jako powrót możesz użyć interfejsu API TrackDependency do śledzenia dowolnej zależności. Testowanie obciążenia często ujawnia tego rodzaju luki w telemetrii, które można skorygować.

Test 2. Zwiększony rozmiar pamięci podręcznej

W drugim teście obciążeniowym zespół deweloperów zwiększył rozmiar pamięci podręcznej w Azure Cache for Redis. (Zobacz Jak skalować Azure Cache for Redis). Ta zmiana rozwiązała wyjątki poza pamięcią, a teraz mapa aplikacji pokazuje błędy zerowe:

Zrzut ekranu przedstawiający mapę aplikacji z informacją o tym, że zwiększenie rozmiaru pamięci podręcznej rozwiązało wyjątki poza pamięcią.

Jednak nadal występuje dramatyczne opóźnienie w przetwarzaniu komunikatów. Podczas szczytu testu obciążeniowego szybkość komunikatów przychodzących przekracza 5× szybkość wychodząca:

Wykres przychodzących i wychodzących komunikatów pokazujący szybkość przychodzących komunikatów jest większa niż 5 razy większa niż szybkość wychodząca.

Poniższy wykres mierzy przepływność w zakresie uzupełniania komunikatów — czyli szybkość, z jaką usługa Workflow oznacza komunikaty usługi Service Bus zgodnie z ukończeniem. Każdy punkt na wykresie reprezentuje 5 sekund danych z maksymalną przepływnością ok. 16/s.

Wykres przepływności komunikatów

Ten graf został wygenerowany przez uruchomienie zapytania w obszarze roboczym usługi Log Analytics przy użyciu języka zapytań Kusto:

let start=datetime("2020-07-31T22:30:00.000Z");
let end=datetime("2020-07-31T22:45:00.000Z");
dependencies
| where cloud_RoleName == 'fabrikam-workflow'
| where timestamp > start and timestamp < end
| where type == 'Azure Service Bus'
| where target has 'https://dev-i-iuosnlbwkzkau.servicebus.windows.net'
| where client_Type == "PC"
| where name == "Complete"
| summarize succeeded=sumif(itemCount, success == true), failed=sumif(itemCount, success == false) by bin(timestamp, 5s)
| render timechart

Test 3. Skalowanie w poziomie usług zaplecza

Wygląda na to, że zaplecze jest wąskim gardłem. Następnym krokiem jest skalowanie usług biznesowych (Package, Delivery i Drone Scheduler) i sprawdzenie, czy przepływność się poprawia. W następnym teście obciążeniowym zespół przeskalował te usługi z trzech replik do sześciu replik.

Ustawienie Wartość
Węzły klastra 6
Usługa pozyskiwania 3 repliki
Usługa przepływu pracy 3 repliki
Pakiet, dostarczanie, usługi Drone Scheduler Każda z 6 replik

Niestety ten test obciążeniowy pokazuje tylko skromną poprawę. Komunikaty wychodzące nadal nie są zgodne z komunikatami przychodzącymi:

Wykres przychodzących i wychodzących komunikatów pokazujący, że komunikaty wychodzące nadal nie nadążają za przychodzącymi komunikatami.

Przepływność jest bardziej spójna, ale wartość maksymalna osiągana jest mniej więcej taka sama jak poprzedni test:

Wykres przepływności komunikatów pokazujący, że wartość maksymalna osiągana jest mniej więcej taka sama jak poprzedni test.

Ponadto, patrząc na szczegółowe informacje o kontenerze usługi Azure Monitor, wydaje się, że problem nie jest spowodowany wyczerpaniem zasobów w klastrze. Po pierwsze metryki na poziomie węzła pokazują, że użycie procesora CPU pozostaje poniżej 40% nawet w 95. percentylu, a wykorzystanie pamięci wynosi około 20%.

Wykres wykorzystania węzła usługi AKS

W środowisku Kubernetes można ograniczyć poszczególne zasobniki nawet wtedy, gdy węzły nie są. Ale widok na poziomie zasobnika pokazuje, że wszystkie zasobniki są w dobrej kondycji.

Wykres wykorzystania zasobnika usługi AKS

Z tego testu wydaje się, że dodanie kolejnych zasobników do zaplecza nie pomoże. Następnym krokiem jest przyjrzenie się bliżej usłudze Przepływu pracy, aby zrozumieć, co się dzieje podczas przetwarzania komunikatów. Usługa Application Insights pokazuje, że średni czas trwania operacji usługi Process Przepływ pracy wynosi 246 ms.

Zrzut ekranu przedstawiający usługę Application Insights

Możemy również uruchomić zapytanie, aby pobrać metryki dotyczące poszczególnych operacji w ramach każdej transakcji:

Docelowego percentile_duration_50 percentile_duration_95
https://dev-i-iuosnlbwkzkau.servicebus.windows.net/ | dev-i-iuosnlbwkzkau 86.66950203 283.4255578
Dostawy 37 57
package 12 17
dronescheduler 21 41

Pierwszy wiersz w tej tabeli reprezentuje kolejkę usługi Service Bus. Pozostałe wiersze to wywołania usług zaplecza. Poniżej przedstawiono zapytanie usługi Log Analytics dla tej tabeli:

let start=datetime("2020-07-31T22:30:00.000Z");
let end=datetime("2020-07-31T22:45:00.000Z");
let dataset=dependencies
| where timestamp > start and timestamp < end
| where (cloud_RoleName == 'fabrikam-workflow')
| where name == 'Complete' or target in ('package', 'delivery', 'dronescheduler');
dataset
| summarize percentiles(duration, 50, 95) by target

Zrzut ekranu przedstawiający wynik zapytania usługi Log Analytics

Opóźnienia te wyglądają rozsądnie. Ale oto kluczowy wgląd: Jeśli całkowity czas operacji wynosi ok. 250 ms, to stawia ściśle górną granicę sposobu przetwarzania szybkich komunikatów w serii. Kluczem do poprawy przepływności jest zatem większa równoległość.

Powinno to być możliwe w tym scenariuszu z dwóch powodów:

  • Są to wywołania sieciowe, więc większość czasu jest poświęcana na oczekiwanie na ukończenie operacji we/wy
  • Komunikaty są niezależne i nie muszą być przetwarzane w kolejności.

Test 4. Zwiększanie równoległości

Na potrzeby tego testu zespół skupił się na zwiększaniu równoległości. W tym celu dostosowali dwa ustawienia klienta usługi Service Bus używane przez usługę Workflow:

Ustawienie Opis Domyślny Nowa wartość
MaxConcurrentCalls Maksymalna liczba komunikatów do przetwarzania współbieżnego. 1 20
PrefetchCount Ile komunikatów klient pobierze z wyprzedzeniem do lokalnej pamięci podręcznej. 0 3000

Aby uzyskać więcej informacji na temat tych ustawień, zobacz Best Practices for performance improvements using Service Bus Messaging (Najlepsze rozwiązania dotyczące poprawy wydajności przy użyciu komunikatów usługi Service Bus). Uruchomienie testu przy użyciu tych ustawień wywołało następujący graf:

Wykres komunikatów przychodzących i wychodzących przedstawiający liczbę komunikatów wychodzących, które rzeczywiście przekraczają łączną liczbę komunikatów przychodzących.

Przypomnij sobie, że komunikaty przychodzące są wyświetlane w kolorze jasnoniebieskim, a komunikaty wychodzące są wyświetlane w ciemnoniebieskim.

Na pierwszy rzut oka jest to bardzo dziwny wykres. Przez pewien czas szybkość komunikatów wychodzących dokładnie śledzi szybkość przychodzącą. Ale następnie, na około 2:03 znacznik, szybkość przychodzących komunikatów jest wyłączona, podczas gdy liczba wychodzących komunikatów nadal rośnie, faktycznie przekraczając całkowitą liczbę przychodzących komunikatów. To wydaje się niemożliwe.

Wskazówki dotyczące tej tajemnicy można znaleźć w widoku Zależności w usłudze Application Insights. Ten wykres zawiera podsumowanie wszystkich wywołań wykonanych przez usługę Workflow w usłudze Service Bus:

Wykres wywołań zależności

Zwróć uwagę, że wpis dla DeadLetter. To wywołania wskazuje, że komunikaty przechodzą do kolejki utraconych komunikatów usługi Service Bus.

Aby zrozumieć, co się dzieje, musisz zrozumieć semantykę Peek-Lock w usłudze Service Bus. Gdy klient używa funkcji Peek-Lock, usługa Service Bus niepodziealnie pobiera i blokuje komunikat. Gdy blokada jest utrzymywana, komunikat nie jest dostarczany do innych odbiorników. Jeśli blokada wygaśnie, komunikat stanie się dostępny dla innych odbiorców. Po maksymalnej liczbie prób dostarczenia (które można skonfigurować), usługa Service Bus umieści komunikaty w kolejce utraconych komunikatów, gdzie można je zbadać później.

Pamiętaj, że usługa Przepływu pracy pobiera wstępnie duże partie komunikatów — 3000 komunikatów jednocześnie). Oznacza to, że całkowity czas przetwarzania każdego komunikatu jest dłuższy, co powoduje przekroczenie limitu czasu komunikatów, powrót do kolejki i ostatecznie przejście do kolejki utraconych komunikatów.

To zachowanie można również zobaczyć w wyjątkach, w których rejestrowane są liczne MessageLostLockException wyjątki:

Zrzut ekranu przedstawiający wyjątki usługi Application Insights z wieloma wyjątkami MessageLostLockException.

Test 5. Zwiększanie czasu trwania blokady

W przypadku tego testu obciążeniowego czas trwania blokady komunikatu został ustawiony na 5 minut, aby zapobiec przekroczeniom limitu czasu blokady. Wykres przychodzących i wychodzących komunikatów pokazuje teraz, że system nadąża za szybkością komunikatów przychodzących:

Wykres przychodzących i wychodzących komunikatów pokazujący, że system nadąża za szybkością przychodzących komunikatów.

W ramach łącznego czasu trwania 8-minutowego testu obciążeniowego aplikacja wykonała 25 K operacji z maksymalną przepływnością wynoszącą 72 operacje na sekundę, co oznacza wzrost maksymalnej przepływności o 400%.

Wykres przepływności komunikatów przedstawiający wzrost maksymalnej przepływności o 400%.

Jednak uruchomienie tego samego testu z dłuższym czasem trwania wykazało, że aplikacja nie może utrzymać tego współczynnika:

Wykres komunikatów przychodzących i wychodzących pokazujący, że aplikacja nie może utrzymać tej szybkości.

Metryki kontenera pokazują, że maksymalne wykorzystanie procesora CPU wynosiło blisko 100%. W tym momencie aplikacja wydaje się być powiązana z procesorem CPU. Skalowanie klastra może teraz poprawić wydajność, w przeciwieństwie do poprzedniej próby skalowania w poziomie.

Wykres wykorzystania węzłów usługi AKS pokazujący, że maksymalne wykorzystanie procesora CPU wynosiło blisko 100%.

Test 6: skalowanie w poziomie usług zaplecza (ponownie)

W przypadku końcowego testu obciążeniowego w serii zespół skalował w poziomie klaster i zasobniki Kubernetes w następujący sposób:

Ustawienie Wartość
Węzły klastra 12
Usługa pozyskiwania 3 repliki
Usługa przepływu pracy 6 replik
Pakowanie, dostarczanie, usługi Drone Scheduler Każda z 9 replik

Ten test spowodował zwiększenie stałej przepływności bez znaczących opóźnień w przetwarzaniu komunikatów. Ponadto wykorzystanie procesora CPU węzłów pozostawało poniżej 80%.

Wykres przepływności komunikatów przedstawiający większą trwałą przepływność bez znaczących opóźnień w przetwarzaniu komunikatów.

Podsumowanie

W tym scenariuszu zidentyfikowano następujące wąskie gardła:

  • Wyjątki braku pamięci w Azure Cache for Redis.
  • Brak równoległości przetwarzania komunikatów.
  • Niewystarczający czas trwania blokady komunikatu, co prowadzi do przekroczenia limitu czasu blokady i komunikatów umieszczanych w kolejce utraconych komunikatów.
  • Wyczerpanie procesora CPU.

Aby zdiagnozować te problemy, zespół programistyczny oparł się na następujących metrykach:

  • Szybkość przychodzących i wychodzących komunikatów usługi Service Bus.
  • Mapa aplikacji w usłudze Application Insights.
  • Błędy i wyjątki.
  • Niestandardowe zapytania usługi Log Analytics.
  • Użycie procesora CPU i pamięci w szczegółowych danych kontenera usługi Azure Monitor.

Następne kroki

Aby uzyskać więcej informacji na temat projektowania tego scenariusza, zobacz Projektowanie architektury mikrousług.