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.
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?
źródło
Odpowiedzi:
Po @DavidSchwartz i @Matt wygląda to jak wątek, blokuje zarządzanie problemem.
Sugeruję:
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.
Użyj pul wątków, jeśli ich nie używasz.
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.
źródło