Lista kontrolna wydajności

Efektywność wydajności to możliwość skalowania obciążenia w celu efektywnego spełnienia wymagań użytkowników i jest jednym z filarów struktury Microsoft Azure Well-Architected Framework. Ta lista kontrolna umożliwia przegląd architektury aplikacji z punktu widzenia wydajności.

Projekt aplikacji

Podziel obciążenie na partycje. Projektowanie części procesu jako dyskretnych i dekompozyowalnych. Zminimalizuj rozmiar każdej części, zachowując się zgodnie ze zwykłymi regułami rozdzielania problemów i zasadą pojedynczej odpowiedzialności. Dzięki temu części składników mogą być dystrybuowane w sposób maksymalizują wykorzystanie poszczególnych jednostek obliczeniowych (takich jak rola lub serwer bazy danych). Ułatwia to również skalowanie aplikacji przez dodanie wystąpień określonych zasobów. W przypadku złożonych domen należy rozważyć przyjęcie architektury mikrousług.

Projektowanie pod celu skalowania. Skalowanie umożliwia aplikacjom reagowanie na zmienne obciążenie przez zwiększenie i zmniejszenie liczby wystąpień ról, kolejek i innych usług, których używają. Jednak aplikacja musi zostać zaprojektowana z tego myślą. Na przykład aplikacja i używane przez nie usługi muszą być bez stanowe, aby umożliwić trasę żądań do dowolnego wystąpienia. Zapobiega to również dodatku lub usuwania określonych wystąpień, aby niekorzystnie wpłynąć na bieżących użytkowników. Należy również zaimplementować konfigurację lub automatyczne wykrywanie wystąpień, gdy są one dodawane i usuwane, aby kod w aplikacji może wykonywać niezbędny routing. Na przykład aplikacja internetowa może używać zestawu kolejek w podejściu okrężym do przekierowania żądań do usług w tle działających w rolach procesu roboczego. Aplikacja internetowa musi być w stanie wykryć zmiany liczby kolejek, aby pomyślnie rozsyłać żądania i równoważyć obciążenie aplikacji.

Skaluj jako jednostkę. Zaplanuj dodatkowe zasoby, aby obsłużyć wzrost. Dla każdego zasobu należy znać górne limity skalowania i użyć fragmentowania lub dekompozycji, aby przekroczyć te limity. Określ jednostki skalowania systemu pod kątem dobrze zdefiniowanych zestawów zasobów. Dzięki temu stosowanie operacji skalowania w zewnątrz jest łatwiejsze i mniej podatne na negatywny wpływ na aplikację dzięki ograniczeniom narzuconym przez brak zasobów w części całego systemu. Na przykład dodanie x ról sieci Web i procesów roboczych może wymagać y dodatkowych kolejek oraz z kont magazynu w celu obsłużenia dodatkowego obciążenia generowanego przez te role. Jednostka skalowania może więc składać się z x ról sieci Web i procesu roboczego, kolejek y i z kont magazynu. Zaprojektuj aplikację tak, aby łatwo się ją skalowało, dodając całe jednostki skalowania. Rozważ użycie wzorca sygnatur wdrożenia do wdrożenia jednostek skalowania.

Unikaj koligacji klienta. Jeśli to możliwe, upewnij się, że aplikacja nie wymaga koligacji. W związku z tym żądania mogą być kierowane do dowolnego wystąpienia, a liczba wystąpień nie ma znaczenia. Pozwala to uniknąć narzutu związanego z przechowywaniem, pobieraniem i utrzymywaniem informacji o stanie dla każdego użytkownika.

Skorzystaj z funkcji skalowania automatycznego platformy. Jeśli platforma hostingu obsługuje funkcję skalowania automatycznego, taką jak skalowanie automatyczne platformy Azure, preferuj mechanizmy niestandardowe lub mechanizmy innych firm, chyba że wbudowany mechanizm nie spełnia Twoich wymagań. Używaj zaplanowanych reguł skalowania tam, gdzie to możliwe, aby zapewnić dostęp do zasobów bez opóźnienia uruchamiania, ale dodaj reaktywne skalowanie automatyczne do reguł tam, gdzie jest to możliwe, aby poradzić sobie z nieoczekiwanymi zmianami zapotrzebowania. Możesz użyć operacji skalowania automatycznego w klasycznym modelu wdrażania, aby dostosować skalowanie automatyczne i dodać liczniki niestandardowe do reguł. Aby uzyskać więcej informacji, zobacz Wskazówki dotyczące skalowania automatycznego.

Odciążaj zadania intensywnie obciążane procesorem CPU i intensywnie obciążają we/wy jako zadania w tle. Jeśli uruchomienie żądania do usługi lub zasyłanie znacznej części zasobów trwa długo, należy odciążyć przetwarzanie tego żądania do oddzielnego zadania. Aby wykonać te zadania, użyj ról procesu roboczego lub zadań w tle (w zależności od platformy hostingu). Ta strategia umożliwia usłudze dalsze otrzymywanie dalszych żądań i dalsze reagowanie. Aby uzyskać więcej informacji, zobacz Wskazówki dotyczące zadań w tle.

Rozdystrybuuj obciążenie dla zadań w tle. Jeśli istnieje wiele zadań w tle lub zadania wymagają dużo czasu lub zasobów, rozkładają pracę na wiele jednostek obliczeniowych (takich jak role procesów roboczych lub zadania w tle). Aby uzyskać jedno z możliwych rozwiązań, zobacz Wzorzec konkurujących odbiorców.

Rozważ przejście na architekturę nieudzielonych. Architektura bez współużytkowania używa niezależnych, samowystarcznych węzłów, które nie mają pojedynczego punktu odpowiedzialności (na przykład usług udostępnionych lub magazynu). Teoretycznie taki system może być skalowany niemal w nieskończoność. Chociaż podejście w pełni nieudzielone nie jest praktyczne w przypadku większości aplikacji, może zapewnić możliwość projektowania w celu zapewnienia lepszej skalowalności. Na przykład unikanie używania stanu sesji po stronie serwera, koligacji klienta i partycjonowania danych to dobre przykłady przechodzenia do architektury bez współużytkowania.

Zarządzanie danymi

Użyj partycjonowania danych. Podziel dane na wiele baz danych i serwerów baz danych lub zaprojektuj aplikację tak, aby używać usług magazynu danych, które mogą zapewnić to partycjonowanie w sposób przezroczysty (na przykład usługi Azure SQL Database Elastyczna baza danych i Azure Table Storage). Takie podejście może pomóc zmaksymalizować wydajność i ułatwić skalowanie. Istnieją różne techniki partycjonowania, takie jak poziomy, pionowy i funkcjonalny. Można użyć kombinacji tych elementów, aby osiągnąć maksymalne korzyści ze zwiększonej wydajności zapytań, prostszej skalowalności, bardziej elastycznego zarządzania, lepszej dostępności i dopasowania typu magazynu do danych, które będą przechowywane. Ponadto rozważ użycie różnych typów magazynu danych dla różnych typów danych, wybierając typy w zależności od tego, jak dobrze są zoptymalizowane pod kątem określonego typu danych. Może to obejmować korzystanie z magazynu tabel, bazy danych dokumentów lub magazynu danych rodziny kolumn, a nie relacyjnej bazy danych lub też bazy danych. Aby uzyskać więcej informacji, zobacz Data partitioning guidance (Wskazówki dotyczące partycjonowania danych).

Projektuj pod celu zachowania spójności ostateczności. Spójność ostateczna zwiększa skalowalność przez skrócenie lub usunięcie czasu potrzebnego do zsynchronizowania powiązanych danych podzielonych na partycje w wielu magazynach. Koszt jest taki, że dane nie zawsze są spójne podczas odczytu, a niektóre operacje zapisu mogą powodować konflikty. Spójność ostateczna jest idealna w sytuacjach, w których te same dane są odczytywane często, ale zapisywane rzadko. Aby uzyskać więcej informacji, zobacz Data Consistency Primer (Elementarz spójności danych).

Zmniejszenie liczby interakcji między składnikami i usługami. Unikaj projektowania interakcji, w których aplikacja jest wymagana do wielu wywołań usługi (z których każde zwraca niewielką ilość danych), a nie pojedynczego wywołania, które może zwrócić wszystkie dane. Jeśli to możliwe, połącz kilka powiązanych operacji w jedno żądanie, gdy wywołanie dotyczy usługi lub składnika, które ma zauważalne opóźnienie. Ułatwia to monitorowanie wydajności i optymalizowanie złożonych operacji. Na przykład użyj procedur składowanych w bazach danych, aby hermetyzować złożoną logikę i zmniejszyć liczbę rund i blokowanie zasobów.

Użyj kolejek, aby wyrównać obciążenie w przypadku szybkich zapisów danych. Skoki zapotrzebowania na usługę mogą przeciążyć usługę i spowodować eskalacji błędów. Aby temu zapobiec, rozważ zaimplementowanie wzorca równoważenia obciążenia opartego na kolejce. Użyj kolejki, która działa jako bufor między zadaniem a wywoływaną przez nie usługą. Może to wygładzić sporadyczne duże obciążenia, które w przeciwnym razie mogą spowodować niepowodzenie usługi lub przeoczynie czasu zadania.

Zminimalizuj obciążenie magazynu danych. Magazyn danych jest często wąskim gardłem przetwarzania, kosztownym zasobem, a skalowanie w zewnątrz często nie jest łatwe. Tam, gdzie to możliwe, należy usunąć logikę (np. przetwarzać dokumenty XML lub obiekty JSON) z magazynu danych i wykonywać przetwarzanie w aplikacji. Na przykład zamiast przekazywać kod XML do bazy danych (innej niż nieprzezroczysty ciąg do przechowywania), serializować lub deserializować kod XML w warstwie aplikacji i przekazywać go w postaci natywnej dla magazynu danych. Zwykle znacznie łatwiej jest skalować aplikację w poziomie niż magazyn danych, dlatego należy spróbować wykonać jak najwięcej operacji przetwarzania intensywnie obciążanych obliczeniami w aplikacji.

Zminimalizuj ilość pobieranych danych. Pobierz tylko dane, których potrzebujesz, określając kolumny i używając kryteriów do wybierania wierszy. Użyj parametrów wartości tabeli i odpowiedniego poziomu izolacji. Użyj mechanizmów, takich jak tagi jednostek, aby uniknąć niepotrzebnych pobierania danych.

Agresywne używanie buforowania. Używaj buforowania wszędzie tam, gdzie to możliwe, aby zmniejszyć obciążenie zasobów i usług, które generują lub dostarczają dane. Buforowanie zazwyczaj nadaje się do danych, które są względnie statyczne lub wymagają znacznego przetwarzania. Buforowanie na wszystkich poziomach, gdzie jest to odpowiednie w każdej warstwie aplikacji, w tym na potrzeby dostępu do danych i generowania interfejsu użytkownika. Aby uzyskać więcej informacji, zobacz Caching Guidance (Wskazówki dotyczące buforowania).

Obsługa wzrostu i przechowywania danych. Ilość danych przechowywanych przez aplikację rośnie wraz z czasem. Ten wzrost zwiększa koszty magazynowania, a także opóźnienia podczas uzyskiwania dostępu do danych, co wpływa na przepływność i wydajność aplikacji. Może być możliwe okresowe archiwizowanie niektórych starych danych, do których nie jest już uzyskiwany dostęp, lub przenoszenie rzadko używanych danych do magazynu długoterminowego, co jest bardziej ekonomiczne, nawet jeśli opóźnienie dostępu jest większe.

Optymalizowanie obiektów transferu danych (DTO) przy użyciu wydajnego formatu binarnego. Jednostki DTO są przekazywane między warstwami aplikacji wiele razy. Minimalizacja rozmiaru zmniejsza obciążenie zasobów i sieci. Jednak zrównoważyć oszczędności z narzutami na konwertowanie danych na wymagany format w każdej lokalizacji, w której są używane. Należy przyjąć format, który ma maksymalne współdziałanie, aby umożliwić łatwe ponowne używanie składnika.

Ustawianie kontroli pamięci podręcznej. Zaprojektuj i skonfiguruj aplikację tak, aby w miarę możliwości używać buforowania danych wyjściowych lub buforowania fragmentów w celu zminimalizowania obciążenia przetwarzania.

Włącz buforowanie po stronie klienta. Aplikacje internetowe powinny włączać ustawienia pamięci podręcznej dla zawartości, która może być buforowana. Ta opcja jest zwykle domyślnie wyłączona. Skonfiguruj serwer tak, aby dostarczał odpowiednie nagłówki sterowania pamięci podręcznej, aby umożliwić buforowanie zawartości na serwerach proxy i klientach.

Użyj usług Azure Blob Storage i Azure Content Delivery Network, aby zmniejszyć obciążenie aplikacji. Rozważ przechowywanie statycznej lub stosunkowo statycznej zawartości publicznej, takiej jak obrazy, zasoby, skrypty i arkusze stylów, w magazynie obiektów blob. Takie podejście zwalnia obciążenie spowodowane przez dynamiczne generowanie tej zawartości dla każdego żądania. Ponadto rozważ użycie narzędzia Content Delivery Network do buforowania tej zawartości i dostarczenia jej do klientów. Użycie Content Delivery Network może poprawić wydajność na kliencie, ponieważ zawartość jest dostarczana z geograficznie najbliższego centrum danych, które zawiera Content Delivery Network pamięci podręcznej. Aby uzyskać więcej informacji, zobacz Content Delivery Network Wskazówki.

Optymalizowanie i dostrajanie SQL i indeksów. Niektóre instrukcje SQL T-instrukcje lub konstrukcje mogą mieć niekorzystny wpływ na wydajność, który można zmniejszyć przez optymalizację kodu w procedurze składowanej. Na przykład należy unikać konwertowania typów daty /godziny na varchar przed porównaniem z wartością literału daty /godziny. Zamiast tego użyj funkcji porównywania dat i godzin. Brak odpowiednich indeksów może również spowalniać wykonywanie zapytań. Jeśli używasz struktury mapowania obiektowego/relacyjnej, dowiedz się, jak to działa i jak może wpłynąć na wydajność warstwy dostępu do danych. Aby uzyskać więcej informacji, zobacz Query Tuning (Dostrajanie zapytań).

Rozważ denormalizację danych. Normalizacja danych pomaga uniknąć duplikacji i niespójności. Jednak utrzymywanie wielu indeksów, sprawdzanie więzów integralności, wykonywanie wielu dostępu do małych fragmentów danych i łączenie tabel w celu ponownego zbierania danych nakłada narzut, który może mieć wpływ na wydajność. Zastanów się, czy pewien dodatkowy wolumin magazynu i duplikacja są dopuszczalne, aby zmniejszyć obciążenie magazynu danych. Należy również rozważyć, czy na samej aplikacji (która jest zwykle łatwiejsza do skalowania) można polegać na przejmiecie zadań, takich jak zarządzanie więzami integralności w celu zmniejszenia obciążenia magazynu danych. Aby uzyskać więcej informacji, zobacz Data partitioning guidance (Wskazówki dotyczące partycjonowania danych).

Implementacja

Zapoznaj się z antywłaszkami wydajności. Zobacz Performance antipatterns for cloud applications (Antywłańce wydajności dla aplikacji w chmurze), aby uzyskać typowe rozwiązania, które mogą powodować problemy ze skalowalnością, gdy aplikacja jest pod naciskiem.

Użyj wywołań asynchronicznych. Używaj kodu asynchronicznego wszędzie tam, gdzie to możliwe, podczas uzyskiwania dostępu do zasobów lub usług, które mogą być ograniczone przez operacje we/wy lub przepustowość sieci lub które mają zauważalne opóźnienie, aby uniknąć blokowania wątku wywołującego.

Unikaj blokowania zasobów i zamiast tego używaj optymistycznego podejścia. Nigdy nie należy blokować dostępu do zasobów, takich jak magazyn lub inne usługi, które mają zauważalne opóźnienie, ponieważ jest to podstawowa przyczyna niskiej wydajności. Zawsze używaj optymistycznych podejść do zarządzania operacjami współbieżnych, takimi jak zapisywanie w magazynie. Zarządzanie konfliktami za pomocą funkcji warstwy magazynu. W aplikacjach rozproszonych dane mogą być tylko ostatecznie spójne.

Kompresowanie wysoce skompresowanych danych w sieciach o dużych opóźnieniach i niskiej przepustowości. W większości przypadków w aplikacji internetowej największą ilość danych wygenerowanych przez aplikację i przekazanych przez sieć stanowią odpowiedzi HTTP na żądania klientów. Kompresja HTTP może znacznie to zmniejszyć, szczególnie w przypadku zawartości statycznej. Może to obniżyć koszty, a także zmniejszyć obciążenie sieci, chociaż kompresja zawartości dynamicznej powoduje zastosowanie ułamkowo wyższego obciążenia na serwerze. W innych, bardziej uogólnionych środowiskach kompresja danych może zmniejszyć ilość przesyłanych danych oraz zminimalizować czas i koszty transferu, ale procesy kompresji i dekompresji są narzuty. W związku z tym kompresji należy używać tylko wtedy, gdy istnieje wyraźny wzrost wydajności. Inne metody serializacji, takie jak kodowanie JSON lub kodowanie binarne, mogą zmniejszyć rozmiar ładunku przy mniejszym wpływie na wydajność, natomiast kod XML prawdopodobnie go zwiększy.

Zminimalizuj czas używania połączeń i zasobów. Utrzymywać połączenia i zasoby tylko tak długo, jak długo będą potrzebne. Na przykład jak najszybciej otwieraj połączenia i zezwalaj na ich powrót do puli połączeń. Uzyskaj zasoby tak szybko, jak to możliwe, i likwiduj je tak szybko, jak to możliwe.

Zminimalizuj wymaganą liczbę połączeń. Połączenia usług absorbują zasoby. Ogranicz wymaganą liczbę i upewnij się, że istniejące połączenia są ponownie używane zawsze, gdy jest to możliwe. Na przykład po przeprowadzeniu uwierzytelniania użyj personifikacji tam, gdzie jest to odpowiednie, aby uruchomić kod jako konkretną tożsamość. Może to pomóc w najlepszym użyciu puli połączeń przez ponowne użycie połączeń.

Uwaga

Interfejsy API dla niektórych usług automatycznie ponownie używać połączeń, pod warunkiem, że są przestrzegane wytyczne specyficzne dla usługi. Ważne jest, aby zrozumieć warunki umożliwiające ponowne użycie połączenia dla każdej usługi używanej przez aplikację.

Wysyłanie żądań w partiach w celu zoptymalizowania użycia sieci. Można na przykład wysyłać i odczytywać komunikaty w partiach podczas uzyskiwania dostępu do kolejki oraz wykonywać wiele operacji odczytu lub zapisu jako partię podczas uzyskiwania dostępu do magazynu lub pamięci podręcznej. Może to pomóc w zmaksymalizowaniu wydajności usług i magazynów danych przez zmniejszenie liczby wywołań w sieci.

Unikaj przechowywania stanu sesji po stronie serwera tam, gdzie to możliwe. Zarządzanie stanem sesji po stronie serwera zwykle wymaga koligacji klienta (czyli kierowania każdego żądania do tego samego wystąpienia serwera), co wpływa na możliwość skalowania systemu. Najlepiej zaprojektować klientów jako bez stanowych w odniesieniu do serwerów, których używają. Jeśli jednak aplikacja musi zachować stan sesji, przechowuj poufne dane lub duże ilości danych na klienta w rozproszonej pamięci podręcznej po stronie serwera, do których mogą uzyskać dostęp wszystkie wystąpienia aplikacji.

Optymalizowanie schematów magazynu tabel. W przypadku korzystania z magazynów tabel, które wymagają, aby nazwy tabel i kolumn zostały przekazane i przetworzone przy użyciu każdego zapytania, takiego jak usługa Azure Table Storage, należy rozważyć użycie krótszych nazw, aby zmniejszyć ten narzut. Nie należy jednak poświęcać czytelności ani możliwości zarządzania przy użyciu zbyt niewielkich nazw.

Tworzenie zależności zasobów podczas wdrażania lub uruchamiania aplikacji. Unikaj powtarzających się wywołań metod, które testuje istnienie zasobu, a następnie utwórz zasób, jeśli nie istnieje. Metody takie jak CloudTable.CreateIfNotExists i CloudQueue.CreateIfNotExists w bibliotece klienta usługi Azure Storage są zgodne z tym wzorcem. Te metody mogą nałożyć znaczne obciążenie, jeśli są wywoływane przed każdym dostępem do tabeli magazynu lub kolejki magazynu. Zamiast:

  • Utwórz wymagane zasoby podczas wdrażania aplikacji lub przy jej pierwszym uruchomieniu (dopuszczalne jest pojedyncze wywołanie funkcji CreateIfNotExists dla każdego zasobu w kodzie startowym roli Sieć Web lub Proces roboczy). Pamiętaj jednak, aby obsługiwać wyjątki, które mogą wystąpić, jeśli kod próbuje uzyskać dostęp do zasobu, który nie istnieje. W takich sytuacjach należy rejestrować wyjątek i prawdopodobnie powiadamiać operatora o braku zasobu.
  • W pewnych okolicznościach odpowiednim może być utworzenie brakującego zasobu w ramach kodu obsługi wyjątków. Należy jednak stosować to podejście z rozwagą, ponieważ nieistnie zasobu może wskazywać na błąd programowania (na przykład błędną nazwę zasobu) lub inny problem na poziomie infrastruktury.

Korzystanie z lekkich platform. Starannie wybierz interfejsy API i struktury, których używasz, aby zminimalizować użycie zasobów, czas wykonywania i ogólne obciążenie aplikacji. Na przykład użycie internetowego interfejsu API do obsługi żądań obsługi może zmniejszyć zużycie aplikacji i zwiększyć szybkość wykonywania, ale może nie być odpowiednie w przypadku zaawansowanych scenariuszy, w których wymagane są dodatkowe możliwości platformy Windows Communication Foundation.

Rozważ zminimalizowanie liczby kont usług. Na przykład użyj określonego konta, aby uzyskać dostęp do zasobów lub usług, które nakładają limit na połączenia lub wykonują lepsze działania w przypadku utrzymania mniejszej liczby połączeń. Takie podejście jest typowe w przypadku usług, takich jak bazy danych, ale może mieć wpływ na możliwość dokładnej inspekcji operacji ze względu na personifikację oryginalnego użytkownika.

Przetestuj profilowanie wydajności i testy obciążeniowe podczas opracowywania, w ramach procedur testowych, a przed wydaniem końcowym, aby upewnić się, że aplikacja wykonuje i skaluje zgodnie z potrzebami. Testowanie powinno mieć miejsce na tym samym typie sprzętu co platforma produkcyjna oraz z tym samym typem i ilościami danych oraz obciążeniem użytkownikami, które napotka w środowisku produkcyjnym. Aby uzyskać więcej informacji, zobacz Testowanie wydajności usługi w chmurze.