Dlaczego mamy nagły skok w czasach reakcji?

12

Mamy interfejs API, który jest implementowany za pomocą ServiceStack, który jest hostowany w IIS. Podczas przeprowadzania testu obciążenia interfejsu API odkryliśmy, że czasy odpowiedzi są dobre, ale szybko się pogarszają, gdy tylko trafimy około 3500 jednoczesnych użytkowników na serwer. Mamy dwa serwery, a gdy uderzasz je 7000 użytkowników, średni czas odpowiedzi wynosi poniżej 500 ms dla wszystkich punktów końcowych. Skrzynki znajdują się za modułem równoważenia obciążenia, więc otrzymujemy 3500 współbieżnych na serwer. Jednak gdy tylko zwiększymy liczbę jednoczesnych użytkowników, zauważymy znaczny wzrost czasu reakcji. Zwiększenie liczby jednoczesnych użytkowników do 5000 na serwer daje nam średni czas odpowiedzi na punkt końcowy około 7 sekund.

Pamięć i procesor na serwerach są dość niskie, zarówno przy dobrych czasach odpowiedzi, jak i po ich pogorszeniu. W szczytowym momencie przy 10 000 równoczesnych użytkowników procesor średnio osiąga nieco poniżej 50%, a pamięć RAM zajmuje około 3-4 GB z 16. To sprawia, że ​​myślimy, że osiągamy gdzieś jakiś limit. Poniższy zrzut ekranu pokazuje niektóre kluczowe liczniki w perfmon podczas testu obciążenia z udziałem łącznie 10.000 użytkowników. Podświetlony licznik to żądania / sekundę. Po prawej stronie zrzutu ekranu widać, że żądania na sekundę stają się bardzo nieregularne. Jest to główny wskaźnik długiego czasu reakcji. Jak tylko zobaczymy ten wzór, zauważamy powolne czasy reakcji w teście obciążenia.

zrzut ekranu perfmon z podświetlonymi żądaniami na sekundę

Jak przejść do rozwiązania tego problemu z wydajnością? Próbujemy ustalić, czy jest to problem z kodowaniem, czy problem z konfiguracją. Czy w web.config lub IIS są jakieś ustawienia, które mogłyby wyjaśnić to zachowanie? Pula aplikacji działa .NET v4.0, a wersja IIS to 7.5. Jedyną zmianą, którą wprowadziliśmy w ustawieniach domyślnych, jest aktualizacja wartości długości kolejki puli aplikacji z 1000 do 5000. Dodaliśmy także następujące ustawienia konfiguracji do pliku Aspnet.config:

<system.web>
    <applicationPool 
        maxConcurrentRequestsPerCPU="5000"
        maxConcurrentThreadsPerCPU="0" 
        requestQueueLimit="5000" />
</system.web>

Więcej szczegółów:

Celem interfejsu API jest łączenie danych z różnych źródeł zewnętrznych i powrót jako JSON. Obecnie używa implementacji pamięci podręcznej InMemory do buforowania pojedynczych połączeń zewnętrznych w warstwie danych. Pierwsze żądanie do zasobu pobierze wszystkie wymagane dane, a wszelkie kolejne żądania dla tego samego zasobu uzyskają wyniki z pamięci podręcznej. Mamy program uruchamiający pamięć podręczną, który jest wdrażany jako proces w tle, który aktualizuje informacje w pamięci podręcznej w określonych odstępach czasu. Dodaliśmy blokowanie wokół kodu, który pobiera dane z zasobów zewnętrznych. Wdrożyliśmy również usługi do pobierania danych ze źródeł zewnętrznych w sposób asynchroniczny, dzięki czemu punkt końcowy powinien być tak wolny jak najwolniejsze wywołanie zewnętrzne (chyba że mamy dane w pamięci podręcznej). Odbywa się to za pomocą klasy System.Threading.Tasks.Task.Czy możemy przekroczyć limit liczby wątków dostępnych dla tego procesu?

Christian Hagelid
źródło
5
Ile rdzeni ma Twój procesor? Być może maksymalnie wykorzystujesz jeden rdzeń. Gdy magiczna liczba wynosi 50%, 25% lub 12,5%, to sugeruje, że osiągnąłeś maksymalny rdzeń iz jakiegoś powodu nie jesteś w stanie użyć innych rdzeni, które siedzą bezczynnie. Sprawdź maksymalny rdzeń.
David Schwartz
1
Czy masz jeden wątek na żądanie? Czy na 5000 wniosków masz 5000 wątków? Jeśli to zrobisz, prawdopodobnie jest to twój problem. Zamiast tego należy utworzyć pulę wątków i użyć puli wątków do przetworzenia żądań, umieszczając je w kolejce w miarę ich wchodzenia do puli wątków. Gdy wątek zakończy się żądaniem, może przetworzyć żądanie z kolejki. Ten rodzaj dyskusji najlepiej sprawdza się w przypadku przepełnienia stosu. Zbyt wiele wątków oznacza zbyt wiele przełączników kontekstu.
Mat.
1
Po prostu sprawdzanie rozsądku tutaj, czy próbowałeś wyłączyć wszystkie procesy w tle i zobaczyć, jakie jest zachowanie JSON zwracającego statyczne dane z pamięci podręcznej? Innymi słowy, zmuszanie JSON do żądania danych statycznych i usuwanie „zewnętrznych połączeń asynchronicznych”, które całkowicie odświeżają pamięć podręczną. Ponadto, w zależności od ilości danych JSON obsługiwanych przy każdym żądaniu, czy pomyślałeś o przepustowości sieci i czy żądania zaczynają się tworzyć kopie zapasowe, ponieważ serwery po prostu nie są w stanie wypchnąć danych wystarczająco szybko?
Robert,
1
+1 do powyższej sugestii Davidsa Powinieneś naprawdę powtórzyć test i dokładnie przyjrzeć się wykorzystaniu każdego rdzenia. Sugeruję zrobienie tego jak najszybciej, aby go wyeliminować, jeśli nic więcej. Po drugie, jestem trochę podejrzliwy w stosunku do twojej pamięci podręcznej. Rywalizacja o blokadę może dokładnie pokazać tego rodzaju zachowanie - w niektórych krytycznych punktach blokady powodują opóźnienia, które z kolei powodują, że blokady są utrzymywane dłużej niż normalnie, powodując punkt krytyczny, gdy rzeczy gwałtownie spadają. Czy możesz udostępnić swój bufor i kod blokujący?
Steve Cook
1
Jaka jest konfiguracja dysku dla serwerów (przy założeniu, że ponieważ są one zrównoważone pod względem obciążenia, konfiguracja dysku jest taka sama)? Czy możesz zamieścić wszystkie specyfikacje dotyczące napędów / serwerów w swoim początkowym poście? Czy rzuciłeś perfmon na dyski na dyskach fizycznych, na których istnieją IIS ORAZ pliki dziennika IIS? Całkiem możliwe, że masz problemy z dyskiem w związku z tym, że 3500 żądań = ponad 3500 dzienników IIS. Jeśli znajdują się na tym samym dysku / partycji, możesz mieć duży problem.
Techie Joe,

Odpowiedzi:

2

Po @DavidSchwartz i @Matt wygląda to jak wątek, blokuje zarządzanie problemem.

Sugeruję:

  1. Zatrzymaj wywołania zewnętrzne i wygenerowaną dla nich pamięć podręczną i uruchom test obciążenia ze statycznymi informacjami zewnętrznymi, aby odrzucić wszelkie problemy niezwiązane z serwerem - środowiskiem.

  2. Użyj pul wątków, jeśli ich nie używasz.

  3. O połączeniach zewnętrznych powiedziałeś: „Zaimplementowaliśmy również usługi do pobierania danych ze źródeł zewnętrznych w sposób asynchroniczny, tak aby punkt końcowy był tak wolny jak najwolniejsze połączenie zewnętrzne (chyba że mamy dane w pamięci podręcznej). „

Pytania są następujące: - Czy sprawdziłeś, czy dane pamięci podręcznej są zablokowane podczas połączenia zewnętrznego lub tylko podczas zapisywania wyniku połączenia zewnętrznego w pamięci podręcznej? (zbyt oczywiste, ale trzeba powiedzieć). - Czy blokujesz całą pamięć podręczną lub jej małe części? (zbyt oczywiste, ale trzeba powiedzieć). - Nawet jeśli są one asynchroniczne, jak często uruchamiane są połączenia zewnętrzne? Nawet jeśli nie działają tak często, mogą zostać zablokowane przez nadmierną liczbę żądań do pamięci podręcznej od wywołań użytkownika, gdy pamięć podręczna jest zablokowana. Ten scenariusz zazwyczaj pokazuje stały procent zużytego procesora, ponieważ wiele wątków czeka w ustalonych odstępach czasu, a także „blokowaniem” należy zarządzać. - Czy sprawdziłeś, czy zadania zewnętrzne oznaczają, że czas reakcji również wydłuża się, gdy pojawia się powolny scenariusz?

Jeśli problem nadal występuje, sugeruję unikanie klasy Task i wykonywanie połączeń zewnętrznych za pośrednictwem tej samej puli wątków, która zarządza żądaniami użytkowników. Ma to na celu uniknięcie poprzedniego scenariusza.

SaintJob 2.0
źródło