Wydajność aplikacji sieciowych w systemie Linux. Wstęp

Aplikacje internetowe są obecnie używane wszędzie, a wśród wszystkich protokołów transportowych lwią część stanowi protokół HTTP. Studiując niuanse tworzenia aplikacji internetowych, większość ludzi nie zwraca uwagi na system operacyjny, w którym te aplikacje faktycznie działają. Oddzielenie rozwoju (Dev) od operacji (Ops) tylko pogorszyło sytuację. Jednak wraz z rozwojem kultury DevOps programiści stają się odpowiedzialni za uruchamianie swoich aplikacji w chmurze, dlatego bardzo przydatne jest dla nich dokładne zapoznanie się z backendem systemu operacyjnego. Jest to szczególnie przydatne, jeśli próbujesz wdrożyć system obsługujący tysiące lub dziesiątki tysięcy jednoczesnych połączeń.

Ograniczenia usług internetowych są bardzo podobne do ograniczeń w innych aplikacjach. Niezależnie od tego, czy są to moduły równoważenia obciążenia, czy serwery baz danych, wszystkie te aplikacje mają podobne problemy w środowisku o wysokiej wydajności. Zrozumienie tych podstawowych ograniczeń i ogólnie sposobów ich przezwyciężenia pomoże ocenić wydajność i skalowalność aplikacji internetowych.

Piszę tę serię artykułów w odpowiedzi na pytania młodych programistów, którzy chcą zostać dobrze poinformowanymi architektami systemów. Niemożliwe jest jasne zrozumienie technik optymalizacji aplikacji w systemie Linux bez zagłębienia się w podstawy ich działania na poziomie systemu operacyjnego. Chociaż istnieje wiele typów aplikacji, w tej serii chcę przyjrzeć się aplikacjom internetowym, a nie aplikacjom komputerowym, takim jak przeglądarka czy edytor tekstu. Ten materiał jest przeznaczony dla programistów i architektów, którzy chcą zrozumieć, jak działają programy w systemie Linux lub Unix i jak je zorganizować w celu uzyskania wysokiej wydajności.

Linux jest serwerownia system operacyjny i najczęściej Twoje aplikacje działają na tym systemie operacyjnym. Chociaż mówię „Linux”, w większości przypadków można bezpiecznie założyć, że mam na myśli ogólnie wszystkie systemy operacyjne podobne do Uniksa. Jednakże nie testowałem dołączonego kodu na innych systemach. Jeśli więc interesujesz się FreeBSD lub OpenBSD, Twoje wyniki mogą się różnić. Kiedy próbuję czegoś specyficznego dla Linuksa, zwracam na to uwagę.

Chociaż możesz wykorzystać tę wiedzę do zbudowania aplikacji od zera i będzie ona doskonale zoptymalizowana, lepiej tego nie robić. Jeśli napiszesz nowy serwer WWW w języku C lub C++ dla aplikacji biznesowej swojej organizacji, może to być Twój ostatni dzień w pracy. Jednak znajomość struktury tych aplikacji pomoże w wyborze istniejących programów. Będziesz mógł porównać systemy oparte na procesach z systemami opartymi na wątkach, a także na zdarzeniach. Zrozumiesz i docenisz, dlaczego Nginx działa lepiej niż Apache httpd, dlaczego aplikacja w Pythonie oparta na Tornado może obsługiwać więcej użytkowników w porównaniu z aplikacją Pythona opartą na Django.

ZeroHTTPd: narzędzie edukacyjne

ZeroHTTPd to serwer WWW, który napisałem od podstaw w C jako narzędzie dydaktyczne. Nie ma żadnych zewnętrznych zależności, w tym dostępu do Redis. Prowadzimy własne procedury Redis. Więcej szczegółów znajdziesz poniżej.

Chociaż moglibyśmy szczegółowo omówić teorię, nie ma nic lepszego niż pisanie kodu, uruchamianie go i porównywanie ze sobą wszystkich architektur serwerów. To najbardziej oczywista metoda. Dlatego napiszemy prosty serwer WWW ZeroHTTPd, korzystając z każdego modelu: opartego na procesach, opartego na wątkach i opartego na zdarzeniach. Sprawdźmy każdy z tych serwerów i zobaczmy, jak radzą sobie w porównaniu ze sobą. ZeroHTTPd jest zaimplementowany w pojedynczym pliku C. Serwer oparty na zdarzeniach zawiera uthash, świetna implementacja tablicy mieszającej zawarta w jednym pliku nagłówkowym. W pozostałych przypadkach nie ma żadnych zależności, aby nie komplikować projektu.

W kodzie znajduje się wiele komentarzy, które pomogą Ci zrozumieć. Będąc prostym serwerem WWW w kilku linijkach kodu, ZeroHTTPd jest także minimalnym frameworkiem do tworzenia stron internetowych. Ma ograniczoną funkcjonalność, ale jest w stanie obsługiwać pliki statyczne i bardzo proste strony „dynamiczne”. Muszę powiedzieć, że ZeroHTTPd jest dobry do nauki tworzenia wysokowydajnych aplikacji dla systemu Linux. Ogólnie rzecz biorąc, większość usług internetowych czeka na żądania, sprawdza je i przetwarza. To jest dokładnie to, co zrobi ZeroHTTPd. To narzędzie do nauki, a nie do produkcji. Nie jest świetny w obsłudze błędów i jest mało prawdopodobne, aby mógł pochwalić się najlepszymi praktykami bezpieczeństwa (o tak, użyłem strcpy) lub sprytne sztuczki języka C. Mam jednak nadzieję, że dobrze spełni swoje zadanie.

Wydajność aplikacji sieciowych w systemie Linux. Wstęp
Strona główna ZeroHTTPd. Może wyświetlać różne typy plików, w tym obrazy

Aplikacja Księga Gości

Nowoczesne aplikacje internetowe zazwyczaj nie ograniczają się do plików statycznych. Mają złożone interakcje z różnymi bazami danych, pamięciami podręcznymi itp. Stworzymy więc prostą aplikację internetową o nazwie „Księga gości”, w której odwiedzający pozostawiają wpisy pod swoim imieniem. W księdze gości przechowywane są wcześniej pozostawione wpisy. Na dole strony znajduje się również licznik odwiedzin.

Wydajność aplikacji sieciowych w systemie Linux. Wstęp
Aplikacja internetowa „Księga Gości” ZeroHTTPd

Licznik gości i wpisy do księgi gości przechowywane są w Redis. Do komunikacji z Redisem zaimplementowane są własne procedury, niezależne od biblioteki zewnętrznej. Nie jestem wielkim fanem wdrażania kodu homebrew, gdy istnieją publicznie dostępne i dobrze przetestowane rozwiązania. Jednak celem ZeroHTTPd jest badanie wydajności Linuksa i dostępu do usług zewnętrznych, podczas gdy obsługa żądań HTTP ma poważny wpływ na wydajność. Musimy w pełni kontrolować komunikację z Redis w każdej z naszych architektur serwerowych. W niektórych architekturach używamy blokowania wywołań, w innych stosujemy procedury oparte na zdarzeniach. Korzystanie z zewnętrznej biblioteki klienta Redis nie zapewni tej kontroli. Dodatkowo nasz mały klient Redis wykonuje tylko kilka funkcji (pobieranie, ustawianie i zwiększanie klucza; pobieranie i dołączanie do tablicy). Ponadto protokół Redis jest niezwykle elegancki i prosty. Nie musisz nawet tego specjalnie uczyć. Sam fakt, że protokół wykonuje całą pracę w około stu linijkach kodu, pokazuje, jak dobrze jest przemyślany.

Poniższy rysunek pokazuje, co robi aplikacja, gdy klient (przeglądarka) zażąda /guestbookURL.

Wydajność aplikacji sieciowych w systemie Linux. Wstęp
Jak działa aplikacja Księga Gości

Kiedy trzeba wystawić stronę księgi gości, następuje jedno wywołanie systemu plików w celu wczytania szablonu do pamięci i trzy wywołania sieciowe do Redis. Plik szablonu zawiera większość treści HTML strony pokazanej na powyższym zrzucie ekranu. Istnieją również specjalne symbole zastępcze dla dynamicznej części treści: postów i licznika gości. Otrzymujemy je od Redis, wstawiamy na stronę i dostarczamy klientowi w pełni ukształtowaną treść. Trzeciego wywołania Redis można uniknąć, ponieważ Redis zwraca nową wartość klucza po zwiększeniu. Jednak w przypadku naszego serwera, który ma architekturę asynchroniczną opartą na zdarzeniach, wiele wywołań sieciowych jest dobrym testem do celów edukacyjnych. Odrzucamy więc wartość zwracaną przez Redis dotyczącą liczby odwiedzających i wysyłamy do niej zapytanie za pomocą osobnego wywołania.

Architektury serwerowe ZeroHTTPd

Budujemy siedem wersji ZeroHTTPd z tą samą funkcjonalnością, ale różnymi architekturami:

  • Wielokrotny
  • Serwer widełkowy (jeden proces potomny na żądanie)
  • Serwer przed forkiem (pre-fork procesów)
  • Serwer z wątkami wykonawczymi (jeden wątek na żądanie)
  • Serwer z możliwością tworzenia wątku wstępnego
  • Oparta na architekturze poll()
  • Oparta na architekturze epoll

Mierzymy wydajność każdej architektury, ładując serwer żądaniami HTTP. Jednak porównując architektury wysoce równoległe, liczba zapytań wzrasta. Testujemy trzy razy i obliczamy średnią.

Metodologia testowania

Wydajność aplikacji sieciowych w systemie Linux. Wstęp
Konfiguracja testowania obciążenia ZeroHTTPd

Ważne jest, aby podczas uruchamiania testów wszystkie komponenty nie działały na tej samej maszynie. W takim przypadku system operacyjny ponosi dodatkowe koszty planowania, ponieważ komponenty konkurują o procesor. Pomiar obciążenia systemu operacyjnego każdej z wybranych architektur serwerów jest jednym z najważniejszych celów tego ćwiczenia. Dodanie większej liczby zmiennych będzie szkodliwe dla procesu. Dlatego najlepiej sprawdza się ustawienie pokazane na powyższym obrazku.

Co robi każdy z tych serwerów?

  • loading.unixism.net: To tutaj działamy ab, narzędzie Apache Benchmark. Generuje obciążenie potrzebne do przetestowania naszych architektur serwerowych.
  • nginx.unixism.net: Czasami chcemy uruchomić więcej niż jedną instancję programu serwera. Aby to zrobić, serwer Nginx z odpowiednimi ustawieniami działa jako moduł równoważenia obciążenia, z którego pochodzi ab do procesów naszego serwera.
  • zerohttpd.unixism.net: Tutaj uruchamiamy nasze programy serwerowe na siedmiu różnych architekturach, po jednym na raz.
  • redis.unixism.net: Na tym serwerze działa demon Redis, na którym przechowywane są wpisy do księgi gości i liczniki gości.

Wszystkie serwery działają na tym samym rdzeniu procesora. Chodzi o to, aby ocenić maksymalną wydajność każdej architektury. Ponieważ wszystkie programy serwerowe są testowane na tym samym sprzęcie, jest to punkt odniesienia dla porównania. Moja konfiguracja testowa składa się z serwerów wirtualnych wynajmowanych od Digital Ocean.

Co mierzymy?

Można mierzyć różne wskaźniki. Oceniamy wydajność każdej architektury w danej konfiguracji, ładując serwery żądaniami na różnych poziomach równoległości: obciążenie rośnie od 20 do 15 000 jednoczesnych użytkowników.

Wyniki testu

Poniższy wykres przedstawia wydajność serwerów o różnych architekturach na różnych poziomach równoległości. Oś Y to liczba żądań na sekundę, oś X to połączenia równoległe.

Wydajność aplikacji sieciowych w systemie Linux. Wstęp

Wydajność aplikacji sieciowych w systemie Linux. Wstęp

Wydajność aplikacji sieciowych w systemie Linux. Wstęp

Poniżej tabela z wynikami.

żądań na sekundę

równoległość
wielokrotny
widelec
przed widelcem
streaming
przed transmisją strumieniową
w.
epoka

20
7
112
2100
1800
2250
1900
2050

50
7
190
2200
1700
2200
2000
2000

100
7
245
2200
1700
2200
2150
2100

200
7
330
2300
1750
2300
2200
2100

300
-
380
2200
1800
2400
2250
2150

400
-
410
2200
1750
2600
2000
2000

500
-
440
2300
1850
2700
1900
2212

600
-
460
2400
1800
2500
1700
2519

700
-
460
2400
1600
2490
1550
2607

800
-
460
2400
1600
2540
1400
2553

900
-
460
2300
1600
2472
1200
2567

1000
-
475
2300
1700
2485
1150
2439

1500
-
490
2400
1550
2620
900
2479

2000
-
350
2400
1400
2396
550
2200

2500
-
280
2100
1300
2453
490
2262

3000
-
280
1900
1250
2502
duży rozrzut
2138

5000
-
duży rozrzut
1600
1100
2519
-
2235

8000
-
-
1200
duży rozrzut
2451
-
2100

10 000
-
-
duży rozrzut
-
2200
-
2200

11 000
-
-
-
-
2200
-
2122

12 000
-
-
-
-
970
-
1958

13 000
-
-
-
-
730
-
1897

14 000
-
-
-
-
590
-
1466

15 000
-
-
-
-
532
-
1281

Z wykresu i tabeli widać, że powyżej 8000 jednoczesnych żądań zostało nam już tylko dwóch graczy: pre-fork i epoll. Wraz ze wzrostem obciążenia serwer oparty na ankietach działa gorzej niż serwer przesyłający strumieniowo. Architektura wstępnego tworzenia wątków jest godną konkurencją dla epoll, co świadczy o tym, jak dobrze jądro Linuksa planuje dużą liczbę wątków.

Kod źródłowy ZeroHTTPd

Kod źródłowy ZeroHTTPd tutaj. Dla każdej architektury istnieje oddzielny katalog.

ZeroHTTPd │ ├── 01_iterative │ ├── main.c ├── 02_forking │ ├── main.c ├── 03_preforking │ ├── main.c ├── 04 _ wątki │ ├── main.c ├── 05_prethreading │ ├── main.c ├── 06_poll │ ├── main.c ├── 07_epoll │ └── main.c ├── Makefile ├── public │ ├── indeks .html │ └── tux .png └── szablony └── księga gości └── indeks.html

Oprócz siedmiu katalogów dla wszystkich architektur, w katalogu najwyższego poziomu znajdują się jeszcze dwa: public i templates. Pierwsza zawiera plik Index.html i obraz z pierwszego zrzutu ekranu. Możesz tam umieścić inne pliki i foldery, a ZeroHTTPd powinien obsłużyć te statyczne pliki bez żadnych problemów. Jeśli ścieżka w przeglądarce odpowiada ścieżce w folderze publicznym, ZeroHTTPd szuka pliku indeks.html w tym katalogu. Treść księgi gości generowana jest dynamicznie. Posiada jedynie stronę główną, a jej zawartość opiera się na pliku 'templates/guestbook/index.html'. ZeroHTTPd z łatwością dodaje dynamiczne strony do rozszerzenia. Pomysł jest taki, że użytkownicy mogą dodawać szablony do tego katalogu i rozszerzać ZeroHTTPd w razie potrzeby.

Aby zbudować wszystkie siedem serwerów, uruchom make all z katalogu najwyższego poziomu - i wszystkie kompilacje pojawią się w tym katalogu. Pliki wykonywalne szukają katalogów public i templates w katalogu, z którego są uruchamiane.

Interfejs API Linuksa

Nie musisz dobrze znać interfejsu API systemu Linux, aby zrozumieć informacje zawarte w tej serii artykułów. Polecam jednak poczytać więcej na ten temat, w Internecie jest wiele źródeł referencyjnych. Chociaż poruszymy kilka kategorii interfejsów API systemu Linux, skupimy się przede wszystkim na procesach, wątkach, zdarzeniach i stosie sieciowym. Oprócz książek i artykułów na temat API Linuksa polecam także lekturę many dotyczącej wywołań systemowych i używanych funkcji bibliotecznych.

Wydajność i skalowalność

Jedna uwaga odnośnie wydajności i skalowalności. Teoretycznie nie ma między nimi żadnego związku. Możesz mieć usługę internetową, która działa bardzo dobrze, z czasem reakcji wynoszącym kilka milisekund, ale w ogóle nie jest skalowana. Podobnie może istnieć słabo działająca aplikacja internetowa, której reakcja zajmuje kilka sekund, ale skaluje się ona dziesiątkami, aby obsłużyć dziesiątki tysięcy jednoczesnych użytkowników. Jednak połączenie wysokiej wydajności i skalowalności to bardzo wydajna kombinacja. Aplikacje o wysokiej wydajności zazwyczaj oszczędnie zużywają zasoby, dzięki czemu efektywnie obsługują większą liczbę jednoczesnych użytkowników na serwerze, redukując koszty.

Zadania procesora i we/wy

Wreszcie, w informatyce zawsze istnieją dwa możliwe typy zadań: dla operacji we/wy i dla procesora. Odbieranie żądań przez Internet (we/wy sieciowe), udostępnianie plików (we/wy sieciowe i dyskowe), komunikacja z bazą danych (we/wy sieciowe i dyskowe) to wszystkie czynności związane z we/wy. Niektóre zapytania do bazy danych mogą obciążać nieco procesor (sortowanie, uśrednianie miliona wyników itp.). Większość aplikacji internetowych jest ograniczona maksymalną możliwą liczbą wejść/wyjść, a procesor rzadko jest wykorzystywany z pełną wydajnością. Kiedy widzisz, że jakieś zadanie we/wy zużywa dużo procesora, najprawdopodobniej jest to oznaką złej architektury aplikacji. Może to oznaczać, że zasoby procesora są marnowane na zarządzanie procesami i przełączanie kontekstu – a to nie jest do końca przydatne. Jeśli zajmujesz się przetwarzaniem obrazu, konwersją plików audio lub uczeniem maszynowym, aplikacja wymaga potężnych zasobów procesora. Jednak w przypadku większości zastosowań tak nie jest.

Dowiedz się więcej o architekturach serwerów

  1. Część I: Architektura iteracyjna
  2. Część druga. Serwery widełkowe
  3. Część III. Serwery przed forkiem
  4. Część IV. Serwery z wątkami wykonawczymi
  5. Część V. Serwery z wątkami
  6. Część VI. Architektura oparta na Polu
  7. Część VII. architektura oparta na epoll

Źródło: www.habr.com

Dodaj komentarz