Уеб приложенията вече се използват навсякъде, а сред всички транспортни протоколи голям дял заема HTTP. Изследвайки нюансите на разработката на уеб приложения, повечето отделят много малко внимание на операционната система, в която тези приложения всъщност се стартират. Разделението на разработка (Dev) и експлоатация (Ops) само влошава ситуацията. Но с разпространението на културата DevOps разработчиците започват да носят отговорност за стартирането на своите приложения в облака, така че е много полезно за тях да се запознаят в детайли с бекенда на операционната система. Това е особено ценно, ако се опитвате да разположите система за хиляди или десетки хиляди едновременни свързвания.
Ограниченията в уеб услугите са много подобни на ограниченията в другите приложения. Независимо дали става въпрос за балансировачи на натоварването или сървъри на бази данни, всички тези приложения имат сходни проблеми в среда с висока производителност. Разбирането на тези фундаментални ограничения и начините за тяхното преодоляване в общи линии ще позволи да се оценят производителността и мащабируемостта на вашите уеб приложения.
Пиша тази серия статии в отговор на въпросите на млади разработчици, които искат да станат добре информирани системни архитекти. Невъзможно е да се разберат ясно методите за оптимизация на приложенията в Linux, без да се задълбочим в основите, как работят на ниво операционна система. Въпреки че има много видове приложения, в този цикъл искам да изследвам мрежови приложения, а не десктоп приложения, като браузъри или текстови редактори. Този материал е насочен към разработчици и архитекти, които искат да разберат как работят програмите в Linux или Unix и как да ги структурират за висока производителност.
Linux е сървърна операционна система и най-често вашите приложения работят именно на тази ОС. Въпреки че казвам "Linux", през по-голямата част от времето можете с увереност да предположите, че става въпрос за всички Unix-подобни операционни системи като цяло. Все пак, не съм тествал придружаващия код на други системи. Така че, ако ви интересуват FreeBSD или OpenBSD, резултатът може да варира. Когато пробвам нещо специфично за Linux, аз го посочвам.
Въпреки че можете да използвате придобитите знания, за да създадете приложение от нулата, и то ще бъде невероятно оптимизирано, е по-добре да не го правите. Ако напишете нов уеб сървър на C или C++ за бизнес приложението на вашата организация, възможно е да е последният ви ден на работа. Все пак, познаването на структурата на тези приложения ще помогне при избора на вече съществуващи програми. Ще можете да сравнявате системи, базирани на процеси, с такива, базирани на потоци, а също и на събития. Ще разберете и оцените защо Nginx работи по-добре от Apache httpd, защо Python приложението, базирано на Tornado, може да обслужва повече потребители в сравнение с Python приложението, базирано на Django.
ZeroHTTPd: учебен инструмент
— уеб сървър, който написах от нулата на C като учебен инструмент. Няма външни зависимости, включително достъп до Redis. Ние стартираме собствени Redis процедури. Повече информация по-долу.
Въпреки че можем да обсъждаме теория дълго време, няма нищо по-добро от това да напишем код, да го стартираме и да сравним различните архитектури на сървъри. Това е най-очевидният метод. Затова ще пишем прост уеб сървър ZeroHTTPd, прилагащ всяка от следните модели: на базата на процеси, потоци и събития. Ще тестваме всеки от тези сървъри и ще видим как работят в сравнение помежду си. ZeroHTTPd е реализиран в един C файл. Съставът на сървъра на базата на събития включва , отлична реализация на хеш-таблица, която се предоставя в един заглавен файл. В останалите случаи няма зависимости, за да не се усложнява проектът.
В кода има много коментари, за да помогнат при разбирането. Будейки прост уеб сървър в няколко реда код, ZeroHTTPd представлява и минимален фреймворк за уеб разработка. Има ограничена функционалност, но е способен да издава статични файлове и много прости „динамични“ страници. Трябва да кажа, че ZeroHTTPd е подходящ за обучение как да се създават високопроизводителни Linux приложения. По същество, повечето уеб услуги чакат заявките, проверяват ги и ги обработват. Именно това ще прави ZeroHTTPd. Това е инструмент за обучение, а не за продукция. Не е особено добър в обработката на грешки и едва ли може да се похвали с най-добрите практики за сигурност (о да, използвах strcpy) или с усовершенствованными трюками на C. Но я надеюсь, той е справится с задачей.

Начална страница на ZeroHTTPd. Той може да предоставя различни типове файлове, включително изображения.
Приложение за книга с гости.
Съвременните уеб приложения обикновено не се ограничават до статични файлове. Те имат сложни взаимодействия с различни бази данни, кешове и т.н. Затова ще изградим просто уеб приложение, наречено „Книга за гости“, където посетителите оставят записи с имената си. В книгата за гости се запазват записи, оставени по-рано. Има и брояч на посетителите в долната част на страницата.

Уеб приложение „Книга за гости“ на ZeroHTTPd.
Броячът на посетителите и записите в книгата за гости се съ хранят в Redis. За комуникация с Redis са реализирани собствени процедури, които не зависят от външна библиотека. Не съм голям привърженик на разработката на самоделни кодове, когато има публични и добре тествани решения. Но целта на ZeroHTTPd е да изследва производителността на Linux и достъпа до външни услуги, докато обработката на HTTP заявки сериозно влияе на производителността. Трябва да имаме пълен контрол над комуникацията с Redis във всяка от нашите архитектури на сървъри. В една архитектура използваме блокиращи повиквания, в други - процедури, базирани на събития. Използването на външна клиентска библиотека Redis не би осигурило такъв контрол. Освен това, нашият малък клиент Redis изпълнява само няколко функции (извличане, настройка и увеличаване на ключа; извличане и добавяне към масив). В допълнение, протоколът на Redis е изключително елегантен и прост. Не е нужно дори специално да се учи. Фактът, че цялата работа по протокола се извършва с около сто реда код, показва колко добре е проектиран.
На следващата схема са показани действията на приложението, когато клиентът (браузерът) прави заявка. /guestbookURL.

Механизмът на работа на приложението за книга с гости.
Когато се издаде страница на книгата за гости, се извършва един повик към файловата система, за да се прочете шаблонът в паметта, и три мрежови повика към Redis. Файлът на шаблона съдържа голяма част от съдържанието на HTML за страницата, показана на екрана в горната част. Там има и специални запълващи места за динамичната част от съдържанието: записи и брой посетители. Ние ги получаваме от Redis, вмъкваме ги на страницата и издаваме напълно оформеното съдържание на клиента. Третото повикване към Redis може да се избегне, тъй като Redis връща новото стойност на ключа при увеличаване. Въпреки това, за нашия сървър с асинхронна архитектура, базирана на събития, множеството мрежови повиквания е добро изпитание в учебни цели. Следователно, ние отбелязваме, че Redis не ни дава стойността на броя на посетителите и го запитваме с отделно повикване.
Сървърни архитектури ZeroHTTPd
Ние изграждаме седем версии на ZeroHTTPd с еднаква функционалност, но с различни архитектури:
- Итеративна
- Форк сървър (един дъщерен процес за запитване)
- Пре-форк сървър (предварително форкиране на процесите)
- Сървър с нишки (една нишка за запитване)
- Сървър с предварително създадени нишки
- Архитектура на база
poll() - Архитектура на база
epoll
Измерваме производителността на всяка архитектура, като натоварваме сървъра с HTTP запитвания. Но при сравнение на архитектури с висока степен на паралелизъм, броят на запитванията се увеличава. Тестоваме три пъти и изчисляваме средното.
Методология за тестване

Настройка за натоварвателно тестване на ZeroHTTPd
Важно е, когато извършвате тестовете, всички компоненти да не работят на една машина. В този случай ОС носи допълнителни разходи за планиране, тъй като компонентите се състезават за CPU. Измерването на разходите на операционната система с всяка от избраните сървърни архитектури е една от най-важните цели на това упражнение. Добавянето на по-голям брой променливи ще бъде пагубно за процеса. Следователно, настройката на изображението по-горе работи най-добре.
Какво прави всеки от тези сървъри
- load.unixism.net: тук стартираме
ab, утилитата Apache Benchmark. Тя генерира необходимото натоварване за тестване на нашите сървърни архитектури. - nginx.unixism.net: понякога искаме да стартираме повече от един екземпляр на сървърната програма. За целта сървърът Nginx с подходящите настройки работи като балансировчик на натоварването, идващо от ab нашите сървърни процеси.
- zerohttpd.unixism.net: тук стартираме нашите сървърни програми на седем различни архитектури, по една на веднъж.
- redis.unixism.net: на този сървър работи демон Redis, където се съхраняват записите в гостната книга и брояча на посетителите.
Всички сървъри работят на едно ядро на процесора. Идеята е да се оцени максималната производителност на всяка от архитектурите. Понеже всички сървърни програми се тестват на едно и също оборудване, това е базовото ниво за тяхното сравнение. Моята тестова инсталация се състои от виртуални сървъри, наети от Digital Ocean.
Какво измерваме?
Могат да се измерват различни показатели. Оценяваме производителността на всяка архитектура в тази конфигурация, натоварвайки сървърите с заявки на различни нива на паралелизъм: натоварването нараства от 20 до 15 000 едновременно потребители.
Резултати от тестовете
На следващата диаграма е показана производителността на сървърите на различната архитектура при различни нива на паралелизъм. По оста y – брой заявки в секунда, по оста x – паралелни връзки.



По-долу е таблица с резултатите.
заявки в секунда
паралелизъм
итеративен
форк
предварително създаване
многопоточен
предварително многопоточен
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
голямо разнообразие
2138
5000
–
голямо разнообразие
1600
1100
2519
–
2235
8000
–
–
1200
голямо разнообразие
2451
–
2100
10 000
–
–
голямо разнообразие
–
2200
–
2200
11 000
–
–
–
–
2200
–
2122
12 000
–
–
–
–
970
–
1958
13 000
–
–
–
–
730
–
1897
14 000
–
–
–
–
590
–
1466
15 000
–
–
–
–
532
–
1281
От графиката и таблицата става ясно, че над 8000 едновременно заявки остават само двама участници: предварително създаване и epoll. С увеличаване на натоварването сървърът на базата на poll работи по-зле от многопоточния. Архитектурата с предварително създаване на потоци представлява достойна конкуренция на epoll: това свидетелства за това колко добре Linux ядрото планира голям брой потоци.
Изходен код на ZeroHTTPd
Изходен код на ZeroHTTPd . За всяка архитектура има отделна директория.
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.htmlВ допълнение към седемте директории за всички архитектури, в основната директория има още две: public и templates. В първата се намира файлът index.html и изображение от първия екранна снимка. Там може да поставите и други файлове и папки, и ZeroHTTPd трябва без проблем да предостави тези статични файлове. Ако пътят в браузъра съвпада с пътя в папката public, ZeroHTTPd търси файла index.html в тази директория. Съдържанието на гостовата книга се генерира динамично. Тя има само главна страница, чийто съдържание се основава на файла ‘templates/guestbook/index.html’. В ZeroHTTPd е лесно да се добавят динамични страници за разширения. Идеята е, че потребителите могат да добавят шаблони в тази директория и да разширяват ZeroHTTPd при необходимост.
За да компилирате всички седем сървъра, стартирайте make all от основната директория — и всички билдове ще се появят в тази директория. Испълнимите файлове търсят директориите public и templates в директорията, от която се стартират.
Linux API
За да разберете информацията в този цикъл от статии, не е необходимо да имате опит с Linux API. Въпреки това, препоръчвам да прочетете повече по темата, в мрежата има много справочни ресурси. Въпреки че ще се докоснем до няколко категории на Linux API, вниманието ни ще бъде съсредоточено предимно върху процесите, нишките, събитията и мрежовия стек. Освен книгите и статиите за Linux API, препоръчвам също да прочетете маните за системните повиквания и използваните библиотечни функции.
Производителност и мащабируемост
Един коментар относно производителността и мащабируемостта. Теоретично между тях няма връзка. Може да имате уеб услуга, която работи много добре, с време за отговор от няколко милисекунди, но която не е мащабируема. Също така може да съществува зле работещо уеб приложение, което изисква няколко секунди за отговор, но което се мащабира до десетки за обработка на десетки хиляди едновременни потребители. Все пак, съчетанието на висока производителност и мащабируемост е много мощно. Високопроизводителни приложения, като цяло, пестят ресурси и по този начин ефективно обслужват повече едновременни потребители на сървъра, намалявайки разходите.
Задачи CPU и I/O
Накрая, в изчисленията винаги съществуват два възможни типа задачи: за I/O и CPU. Получаване на заявки през интернет (мрежов вход-изход), обслужване на файлове (мрежов и дисков вход-изход), комуникация с база данни (мрежов и дисков вход-изход) — всичко това са действия I/O. Някои заявки към базата данни могат леко да натоварват CPU (сортировка, изчисляване на средното аритметично на милион резултати и т.н.). Повечето уеб приложения са ограничени по максимално възможен I/O, а процесорът рядко се използва на пълна мощност. Когато видите, че в някаква задача за вход-изход се използва много CPU, това най-вероятно е признак на лоша архитектура на приложението. Това може да означава, че ресурсите на CPU се харчат за управление на процесите и превключване на контекста — и това не е особено полезно. Ако правите нещо като обработка на изображения, преобразуване на аудиофайлове или машинно обучение, тогава приложението изисква мощни ресурси CPU. Но за повечето приложения не е така.
По-подробно за серверните архитектури
Източник: habr.com
