Aplikacje webowe są obecnie powszechnie używane, a wśród wszystkich protokołów transportowych HTTP zajmuje znaczną część. Podczas badania niuansów tworzenia aplikacji webowych, większość osób poświęca bardzo mało uwagi systemowi operacyjnemu, na którym te aplikacje są rzeczywiście uruchamiane. Podział między rozwojem (Dev) a operacjami (Ops) jedynie pogarszał sytuację. Jednak wraz z rozprzestrzenieniem się kultury DevOps, programiści zaczynają brać odpowiedzialność za uruchamianie swoich aplikacji w chmurze, dlatego bardzo przydatne jest dokładne zapoznanie się z backendem systemu operacyjnego. Jest to szczególnie pomocne, jeśli starasz się wdrożyć system dla tysięcy lub dziesiątek tysięcy jednoczesnych połączeń.
Ograniczenia w usługach webowych są bardzo podobne do ograniczeń w innych aplikacjach. Niezależnie od tego, czy są to load balancery, czy serwery baz danych, wszystkie te aplikacje mają podobne problemy w wysokowydajnym środowisku. Zrozumienie tych podstawowych ograniczeń oraz sposobów ich przezwyciężania pozwoli ocenić wydajność i skalowalność Twoich aplikacji webowych.
Piszę tę serię artykułów w odpowiedzi na pytania młodych programistów, którzy chcą stać się dobrze poinformowanymi architektami systemów. Niemożliwe jest dokładne zrozumienie metod optymalizacji aplikacji Linux, nie zagłębiając się w podstawy ich działania na poziomie systemu operacyjnego. Chociaż istnieje wiele typów aplikacji, w tej serii chcę zbadać aplikacje sieciowe, a nie desktopowe, takie jak przeglądarka czy edytor tekstu. Materiał ten jest skierowany do programistów i architektów, którzy chcą zrozumieć, jak działają programy Linux lub Unix i jak je strukturyzować dla wysokiej wydajności.
Linux to system operacyjny , a najczęściej Twoje aplikacje działają właśnie na tym systemie. Chociaż mówię „Linux”, przez większość czasu możesz z pewnością założyć, że mam na myśli wszystkie systemy operacyjne podobne do Unix. Niemniej jednak, nie testowałem towarzyszącego kodu na innych systemach. Tak więc, jeśli interesuje Cię FreeBSD lub OpenBSD, wyniki mogą się różnić. Kiedy testuję coś specyficznego dla Linux, zaznaczam to.
Chociaż możesz wykorzystać zdobytą wiedzę do stworzenia aplikacji od podstaw, co może być wspaniale zoptymalizowane, lepiej tego nie robić. Jeśli napiszesz nowy serwer WWW w 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 już istniejących programów. Będziesz mógł porównywać systemy oparte na procesach z systemami opartymi na wątkach oraz na zdarzeniach. Zrozumiesz i docenisz, dlaczego Nginx działa lepiej niż Apache httpd, a także dlaczego aplikacja Python oparta na Tornado może obsługiwać więcej użytkowników w porównaniu do aplikacji Python opartej na Django.
ZeroHTTPd: narzędzie edukacyjne
— serwer WWW, który napisałem od podstaw w C jako narzędzie edukacyjne. Nie ma żadnych zewnętrznych zależności, w tym dostępu do Redis. Uruchamiamy nasze własne procedury Redis. Więcej informacji poniżej.
Chociaż moglibyśmy długo dyskutować o teorii, nie ma nic lepszego niż napisanie kodu, uruchomienie go i porównanie różnych architektur serwerowych. To najbardziej wymowna metoda. Dlatego napiszemy prosty serwer WWW ZeroHTTPd, stosując każdy model: oparty na procesach, wątkach i zdarzeniach. Sprawdzimy każdy z tych serwerów i zobaczymy, jak działają w porównaniu ze sobą. ZeroHTTPd jest zaimplementowany w jednym pliku C. Skład serwera opartego na zdarzeniach zawiera , doskonałą implementację tabeli haszującej, którą dostarczono w jednym pliku nagłówkowym. W pozostałych przypadkach nie ma żadnych zależności, aby nie komplikować projektu.
Kod zawiera wiele komentarzy, aby pomóc w zrozumieniu. Będąc prostym serwerem WWW w kilku linijkach kodu, ZeroHTTPd jest również minimalnym frameworkiem do tworzenia aplikacji webowych. Ma ograniczoną funkcjonalność, ale jest w stanie zwracać pliki statyczne oraz bardzo proste 'dynamiczne' strony. Muszę powiedzieć, że ZeroHTTPd jest doskonałym narzędziem do nauki, jak tworzyć wysokowydajne aplikacje w systemie Linux. W zasadzie większość usług internetowych oczekuje na zapytania, sprawdza je i przetwarza. Dokładnie to będzie robić ZeroHTTPd. To narzędzie edukacyjne, a nie produkcyjne. Nie jest mocny w obsłudze błędów i raczej nie zaoferuje najlepszych praktyk bezpieczeństwa (o tak, użyłem strcpy) lub zawiłymi sztuczkami języka C. Ale mam nadzieję, że poradzi sobie dobrze ze swoim zadaniem.

Strona główna ZeroHTTPd. Może serwować różne typy plików, w tym obrazy.
Aplikacja księgi gości.
Nowoczesne aplikacje webowe zazwyczaj nie ograniczają się do statycznych plików. Mają skomplikowane interakcje z różnymi bazami danych, pamięciami podręcznymi itp. Dlatego stworzymy prostą aplikację webową o nazwie „Księga gości”, w której odwiedzający zostawiają wpisy podpisując się. W księdze gości przechowywane są wcześniejsze wpisy. Jest też licznik odwiedzin na dole strony.

Aplikacja webowa „Księga gości” ZeroHTTPd.
Licznik odwiedzin i wpisy księgi gości są przechowywane w Redis. Dla komunikacji z Redis zaimplementowano własne procedury, niezależne od zewnętrznej biblioteki. Nie jestem wielkim fanem korzystania z domowego kodu, gdy istnieją publiczne i dobrze przetestowane rozwiązania. Ale celem ZeroHTTPd jest poznanie wydajności Linuxa i dostępu do zewnętrznych usług, podczas gdy obsługa żądań HTTP poważnie wpływa na wydajność. Musimy mieć pełną kontrolę nad komunikacją z Redis w każdej z naszych architektur serwerowych. W jednej architekturze używamy blokujących wywołań, w innych procedur bazujących na zdarzeniach. Użycie zewnętrznej biblioteki klienckiej Redis nie da nam takiej kontroli. Ponadto, nasz mały klient Redis wykonuje tylko kilka funkcji (pobieranie, ustawianie i zwiększanie klucza; pobieranie i dodawanie do tablicy). Dodatkowo, protokół Redis jest niezwykle elegancki i prosty. Nie trzeba go nawet specjalnie uczyć. Sam fakt, że cała praca protokołu wykonywana jest w około stu liniach kodu, świadczy o tym, jak dobrze jest przemyślany.
Na następnym obrazku przedstawione są działania aplikacji, gdy klient (przeglądarka) wysyła żądanie. /guestbookURL.

Mechanizm działania aplikacji księgi gości.
Kiedy trzeba wygenerować stronę księgi gości, odbywa się jedno wywołanie do systemu plików w celu odczytania szablonu do pamięci oraz trzy wywołania sieciowe do Redis. Plik szablonu zawiera większość zawartości HTML dla strony na zrzucie ekranu powyżej. Zawiera również specjalne znaczniki dla dynamicznej części treści: wpisów oraz licznika odwiedzających. Pobieramy je z Redis, wstawiamy na stronę i wydajemy klientowi w pełni sformułowaną treść. Trzeciego wywołania do Redis można uniknąć, ponieważ Redis zwraca nową wartość klucza przy inkrementacji. Jednak dla naszego serwera o architekturze asynchronicznej opartej na zdarzeniach, wiele wywołań sieciowych stanowi dobre ćwiczenie edukacyjne. W związku z tym porzucamy zwracana wartość Redis dotyczącą liczby odwiedzających i prosimy o nią oddzielnym wywołaniem.
Architektury serwerowe ZeroHTTPd
Budujemy siedem wersji ZeroHTTPd z identyczną funkcjonalnością, ale różnymi architekturami:
- Iteracyjna
- Serwer fork (jeden proces potomny na żądanie)
- Serwer pre-fork (wstępne forkowanie procesów)
- Serwer z wątkami wykonawczymi (jeden wątek na żądanie)
- Serwer z wstępnym tworzeniem wątków
- Architektura oparta na
poll() - Architektura oparta na
epoll
Mierzymy wydajność każdej architektury, obciążając serwer zapytaniami HTTP. Jednak podczas porównywania architektur z wysokim stopniem równoległości liczba zapytań wzrasta. Testujemy trzy razy i obliczamy średnią.
Metodologia testowania

Konfiguracja do testowania obciążeniowego ZeroHTTPd
Ważne jest, aby podczas przeprowadzania testów wszystkie komponenty nie działały na jednej maszynie. W przeciwnym razie system operacyjny niesie dodatkowe koszty związane z planowaniem, ponieważ komponenty rywalizują o CPU. Pomiar obciążenia systemu operacyjnego z każdą z wybranych architektur serwerowych jest jednym z najważniejszych celów tego ćwiczenia. Dodanie większej liczby zmiennych będzie miało negatywny wpływ na proces. Dlatego konfiguracja na powyższym rysunku działa najlepiej.
Co robi każdy z tych serwerów
- load.unixism.net: tutaj uruchamiamy
ab, narzędzie Apache Benchmark. Generuje ono obciążenie niezbędne do testowania naszych architektur serwerowych. - nginx.unixism.net: czasami chcemy uruchomić więcej niż jeden egzemplarz programu serwerowego. W tym celu serwer Nginx z odpowiednimi ustawieniami działa jako równoważnik obciążenia, przychodzącego z ab naszych procesów serwerowych.
- zerohttpd.unixism.net: tutaj uruchamiamy nasze programy serwerowe na siedmiu różnych architekturach, jedna po drugiej.
- redis.unixism.net: na tym serwerze działa demon Redis, w którym przechowywane są wpisy w księdze gości i licznik odwiedzin.
Wszystkie serwery działają na jednym rdzeniu procesora. Idea polega na ocenie maksymalnej wydajności każdej z architektur. Ponieważ wszystkie programy serwerowe są testowane na tym samym sprzęcie, to jest to poziom bazowy dla ich porównania. Moja instalacja testowa składa się z wirtualnych serwerów wynajmowanych od Digital Ocean.
Co mierzymy?
Można zmierzyć różne wskaźniki. Oceniamy wydajność każdej architektury w danej konfiguracji, obciążając serwery zapytaniami na różnych poziomach równoległości: obciążenie wzrasta od 20 do 15 000 jednoczesnych użytkowników.
Wyniki testów
Na następującym wykresie przedstawiona jest wydajność serwerów na różnych architekturach przy różnych poziomach równoległości. Oś y — liczba zapytań na sekundę, oś x — połączenia równoległe.



Poniżej tabela z wynikami.
zapytania na sekundę
równoległość
iteracyjny
forka
pre-fork
wątkowy
pre-wątkowy
poll
epoll
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ża rozbieżność
2138
5000
–
duża rozbieżność
1600
1100
2519
–
2235
8000
–
–
1200
duża rozbieżność
2451
–
2100
10 000
–
–
duża rozbieżność
–
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 zapytań pozostają tylko dwa gracze: pre-fork i epoll. Wraz ze wzrostem obciążenia serwer oparty na poll działa gorzej niż wątkowy. Architektura z wcześniejszym tworzeniem wątków stanowi godną konkurencję dla epoll: świadczy to o tym, jak dobrze jądro Linux planuje dużą liczbę wątków.
Kod źródłowy ZeroHTTPd
Kod źródłowy ZeroHTTPd . Dla każdej architektury osobny katalog.
ZeroHTTPd
│
├── 01_iterative
│ ├── main.c
├── 02_forking
│ ├── main.c
├── 03_preforking
│ ├── main.c
├── 04_threading
│ ├── main.c
├── 05_prethreading
│ ├── main.c
├── 06_poll
│ ├── main.c
├── 07_epoll
│ └── main.c
├── Makefile
├── public
│ ├── index.html
│ └── tux.png
└── templates
└── guestbook
└── index.htmlOprócz siedmiu katalogów dla wszystkich architektur, w katalogu głównym znajdują się jeszcze dwa: public i templates. W pierwszym znajduje się plik index.html oraz obrazek z pierwszego zrzutu ekranu. Można tam umieścić inne pliki i foldery, a ZeroHTTPd powinien bez problemu zrealizować te statyczne pliki. Jeśli ścieżka w przeglądarce odpowiada ścieżce w folderze public, to ZeroHTTPd szuka w tym katalogu pliku index.html. Zawartość książki gości generowana jest dynamicznie. Ma ona tylko stronę główną, a jej zawartość oparta jest na pliku 'templates/guestbook/index.html'. W ZeroHTTPd łatwo dodaje się dynamiczne strony do rozbudowy. Idea polega na tym, że użytkownicy mogą dodawać do tego katalogu szablony i rozszerzać ZeroHTTPd w miarę potrzeby.
Aby zbudować wszystkie siedem serwerów, uruchom make all z katalogu głównego – a wszystkie buildy pojawią się w tym katalogu. Pliki wykonywalne szukają katalogów public i templates w tym katalogu, z którego są uruchamiane.
Linux API
Aby zrozumieć informacje w tej serii artykułów, nie trzeba dobrze rozumieć Linux API. Jednak polecam przeczytać więcej na ten temat, w sieci jest wiele zasobów informacyjnych. Choć omówimy kilka kategorii Linux API, nasza uwaga skupi się głównie na procesach, wątkach, zdarzeniach i stosie sieciowym. Oprócz książek i artykułów na temat Linux API, polecam również zapoznać się z manualami dla wywołań systemowych i używanych funkcji bibliotecznych.
Wydajność i skalowalność
Jedna uwaga dotycząca 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 odpowiedzi rzędu kilku milisekund, ale wcale nie jest skalowalna. Podobnie może istnieć źle działająca aplikacja internetowa, która wymaga kilku sekund na odpowiedź, ale jest skalowalna na dziesiątki, aby obsłużyć dziesiątki tysięcy równoczesnych użytkowników. Niemniej jednak, połączenie wysokiej wydajności i skalowalności to bardzo potężne połączenie. Wysokowydajne aplikacje generalnie ekonomicznie wykorzystują zasoby i w ten sposób efektywnie obsługują większą liczbę równoczesnych użytkowników na serwerze, obniżając koszty.
Zadania CPU i I/O
W obliczeniach zawsze istnieją dwa rodzaje zadań: dotyczące I/O i CPU. Odbieranie zapytań przez Internet (wejście/wyjście sieciowe), zarządzanie plikami (wejście/wyjście sieciowe i dyskowe), komunikacja z bazą danych (wejście/wyjście sieciowe i dyskowe) — to wszystko są działania I/O. Niektóre zapytania do bazy danych mogą lekko obciążać CPU (sortowanie, obliczanie średniej z miliona wyników itp.). Większość aplikacji webowych ma ograniczenia co do maksymalnego I/O, a procesor rzadko działa na pełnej mocy. Kiedy widzisz, że w jakimś zadaniu wejścia/wyjścia wykorzystywana jest duża moc CPU, najprawdopodobniej jest to oznaka słabej architektury aplikacji. Może to oznaczać, że zasoby CPU są wykorzystywane na zarządzanie procesami i przełączanie kontekstu — co nie jest zbyt użyteczne. Jeśli robisz coś jak przetwarzanie obrazów, konwersja plików audio lub uczenie maszynowe, aplikacja wymaga dużych zasobów CPU. Lecz dla większości aplikacji tak nie jest.
Szczegóły dotyczące architektur serwerowych
Źródło: habr.com
