Продуктивність мережних програм Linux. Вступ

Веб-застосунки нині використовуються повсюдно, а серед усіх транспортних протоколів левову частку займає HTTP. Вивчаючи нюанси розробки веб-додатків, більшість приділяє дуже мало уваги операційній системі, де ці програми реально запускаються. Поділ розробки (Dev) та експлуатації (Ops) лише погіршував ситуацію. Але з поширенням культури DevOps розробники починають відповідати за запуск своїх додатків у хмарі, тому для них дуже корисно досконально познайомитися з бекендом операційної системи. Це особливо корисно, якщо ви намагаєтеся розгорнути систему для тисяч або десятків тисяч одночасних підключень.

Обмеження у веб-службах дуже схожі на обмеження в інших програмах. Будь то балансувальники навантаження або сервери БД, у всіх цих програм аналогічні проблеми у високопродуктивному середовищі. Розуміння цих фундаментальних обмежень та способів їх подолання загалом дозволить оцінити продуктивність та масштабованість ваших веб-додатків.

Я пишу цю серію статей у відповідь на питання молодих розробників, які хочуть стати добре поінформованими системними архітекторами. Неможливо чітко зрозуміти методи оптимізації програм Linux, не занурившись в основи, як вони працюють на рівні операційної системи. Хоча є багато типів додатків, у цьому циклі я хочу дослідити мережеві додатки, а не десктопні, такі як браузер або текстовий редактор. Цей матеріал розрахований на розробників та архітекторів, які хочуть зрозуміти, як працюють програми Linux або Unix та як їх структурувати для високої продуктивності.

Linux - це серверна операційна система, і найчастіше ваші програми працюють саме на цій ОС. Хоча я говорю «Linux», більшу частину часу ви можете з упевненістю припустити, що маються на увазі всі Unix-подібні операційні системи в цілому. Проте я не тестував супроводжуючий код на інших системах. Отже, якщо вас цікавить FreeBSD або OpenBSD, результат може відрізнятись. Коли пробую щось Linux-специфічне, то вказую на це.

Хоча ви можете використовувати отримані знання для створення програми з нуля, і вона буде чудово оптимізована, але краще так не робити. Якщо ви напишете новий веб-сервер на C або C++ для бізнес-програми своєї організації, можливо, це буде ваш останній день на роботі. Однак знання структури цих програм допоможе у виборі вже існуючих програм. Ви зможете порівнювати системи на основі процесів із системами на основі потоків, а також на основі подій. Ви зрозумієте та оціните, чому Nginx працює краще, ніж Apache httpd, чому програма Python на основі Tornado може обслуговувати більше користувачів у порівнянні з програмою Python на основі Django.

ZeroHTTPd: інструмент навчання

ZeroHTTPd — веб-сервер, який я написав з нуля на C як навчальний інструмент. Він не має зовнішніх залежностей, у тому числі доступу до Redis. Ми запускаємо власні процедури Redis. Докладніше див. нижче.

Хоча ми могли б довго обговорювати теорію, немає нічого кращого, ніж написати код, запустити його і порівняти між собою всі серверні архітектури. Це найнаочніший метод. Тому ми писатимемо простий веб-сервер ZeroHTTPd, застосовуючи кожну модель: на основі процесів, потоків та подій. Перевіримо кожен із цих серверів і подивимося, як вони працюють у порівнянні один з одним. ZeroHTTPd реалізований в одному файлі C. До складу сервера на основі подій входить уташ, відмінна реалізація хеш-таблиці, яка поставляється в одному заголовному файлі В інших випадках жодних залежностей немає, щоб не ускладнювати проект.

У коді дуже багато коментарів, щоб допомогти розібратися. Будучи простим веб-сервером у кількох рядках коду, ZeroHTTPd також є мінімальним фреймворком для веб-розробки. Він має обмежену функціональність, але він здатний видавати статичні файли і дуже прості «динамічні» сторінки. Мушу сказати, що ZeroHTTPd добре підходить для навчання, як створювати високопродуктивні Linux-програми. За великим рахунком, більшість веб-сервісів чекають на запити, перевіряють їх і обробляють. Саме це робитиме ZeroHTTPd. Це інструмент для навчання, а не для продакшну. Він не сильний в обробці помилок і навряд чи похвалюється найкращими практиками безпеки (о так, я використав strcpy) або хитромудрими трюками мови C. Але я сподіваюся, він добре впорається зі своїм завданням.

Продуктивність мережних програм Linux. Вступ
Головна сторінка ZeroHTTPd. Він може видавати різні типи файлів, включаючи зображення

Додаток гостьової книги

Сучасні веб-програми зазвичай не обмежені статичними файлами. У них складні взаємодії з різними БД, кешами і т.д. Тому ми створимо просте веб-додаток під назвою «Гостьова книга», де відвідувачі залишають записи під своїми іменами. У гостьовій книзі зберігаються записи, залишені раніше. Є також лічильник відвідувачів у нижній частині сторінки.

Продуктивність мережних програм Linux. Вступ
Веб-додаток «Гостьова книга» ZeroHTTPd

Лічильник відвідувачів та записи гостьової книги зберігаються у Redis. Для комунікацій з Redis реалізовані власні процедури, вони залежать від зовнішньої бібліотеки. Я не великий шанувальник викочувати доморощений код, коли є загальнодоступні та добре протестовані рішення. Але мета ZeroHTTPd – вивчити продуктивність Linux та доступ до зовнішніх служб, тоді як обслуговування HTTP-запитів серйозно впливає на продуктивність. Ми повинні повністю контролювати комунікації з Redis у кожній з наших серверних архітектур. В одній архітектурі ми використовуємо блокуючі виклики, в інших процедури на основі подій. Використання зовнішньої клієнтської бібліотеки Redis не дасть такого контролю. Крім того, наш маленький клієнт Redis виконує лише кілька функцій (отримання, налаштування та збільшення ключа; отримання та додавання до масиву). До того ж, протокол Redis винятково елегантний та простий. Його навіть вивчати спеціально не треба. Сам факт, що всю роботу протокол виконує приблизно в ста рядках коду, говорить про те, наскільки він добре продуманий.

На наступному малюнку показані дії програми, коли клієнт (браузер) запитує /guestbookURL.

Продуктивність мережних програм Linux. Вступ
Механізм роботи програми гостьової книги

Коли потрібно видати сторінку гостьової книги, один виклик до файлової системи для читання шаблону в пам'ять і три мережевих виклики до Redis. Файл шаблону містить більшу частину вмісту HTML для сторінки на скріншоті вгорі. Там є також спеціальні заповнювачі для динамічної частини контенту: записів та лічильника відвідувачів. Ми отримуємо їх із Redis, вставляємо на сторінку та видаємо клієнту повністю сформований контент. Третій виклик Redis можна уникнути, оскільки Redis повертає нове значення ключа при збільшенні. Однак для нашого сервера з асинхронною архітектурою на основі подій безліч мережевих дзвінків — гарне випробування з метою навчання. Таким чином, ми відкидаємо значення Redis, що повертається, про кількість відвідувачів і запитуємо його окремим викликом.

Серверні архітектури ZeroHTTPd

Ми будуємо сім версій ZeroHTTPd з однаковою функціональністю, але різними архітектурами:

  • Ітеративна
  • Форк сервер (один дочірній процес на запит)
  • Префорк сервер (попередній форкінг процесів)
  • Сервер з потоками виконання (один thread на запит)
  • Сервер із попереднім створенням потоків
  • Архітектура на базі poll()
  • Архітектура на базі epoll

Вимірюємо продуктивність кожної архітектури завантаживши сервер HTTP-запитами. Але при порівнянні архітектур з високим ступенем паралелізму кількість запитів зростає. Тестуємо три рази та вважаємо середню.

Методологія тестування

Продуктивність мережних програм Linux. Вступ
Установка для тестування навантаження 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 – паралельні з'єднання.

Продуктивність мережних програм Linux. Вступ

Продуктивність мережних програм Linux. Вступ

Продуктивність мережних програм Linux. Вступ

Нижче таблиця з результатами.

запитів за секунду

паралелізм
ітеративний
вилка
пре-форк
потоковий
пре-струмовий
голосування
еполл

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 05_prethreading │ ├── main.c ├── 06_poll │ ├── main.c ├── 07_epoll │ └── main.c ├── Makefile ├── public │ ├─── index.html png └── templates └── guestbook └── index.html

Окрім семи директорій для всіх архітектур, у каталозі верхнього рівня є ще дві: public та templates. У першому лежить файл index.html та зображення з першого скріншота. Туди можна помістити інші файли та папки, і ZeroHTTPd має без проблем видати ці статичні файли. Якщо path у браузері відповідає шляху в папці 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. Але для більшості програм це не так.

Детальніше про серверні архітектури

  1. Частина I. Ітеративна архітектура
  2. Частина ІІ. Форк-сервери
  3. Частина ІІІ. Пре-Форк сервери
  4. Частина IV. Сервери з потоками виконання
  5. Частина V. Сервери із попереднім створенням потоків
  6. Частина VI. Архітектура на базі poll
  7. Частина VII. Архітектура на базі epoll

Джерело: habr.com

Додати коментар або відгук