Типи веб-серверів або Чому Apache

Спочатку потрібно пояснити, як взагалі працюють мережеві сервери. Ті, хто знайомий із мережевим програмуванням, знають, що по суті існують три моделі роботи сервера:

  1. Послідовна. Сервер відкриває сокет, що слухає, і чекає, коли з'явиться з'єднання (під час очікування він знаходиться в заблокованому стані). Коли з'єднання приходить, сервер обробляє його в тому ж контексті, закриває з'єднання і знову чекає з'єднання. Очевидно, це далеко не найкращий спосіб, особливо коли робота з клієнтом ведеться досить довго і багато підключень. Крім того, послідовна модель має ще багато недоліків (наприклад, неможливість використання кількох процесорів), і в реальних умовах вона практично не використовується.
  2. Багатопроцесна (багатопоточна). Сервер відкриває сокет, що слухає. Коли приходить з'єднання, він приймає його, після чого створює (або бере з пулу заздалегідь створених) новий процес або потік, який може як завгодно довго працювати зі з'єднанням, а по закінченні роботи завершитися або повернутися в пул. Головний потік тим часом готовий прийняти нове з'єднання. Це найбільш популярна модель, тому що вона відносно просто реалізується, дозволяє виконувати складні та довгі обчислення для кожного клієнта та використовувати усі доступні процесори. Приклад її використання – Web-сервер Apache. Однак цей підхід має й недоліки: при великій кількості одночасних підключень створюється дуже багато потоків (або, що ще гірше, процесів), і операційна система витрачає багато ресурсів на перемикання контексту. Особливо погано, коли клієнти дуже повільно приймають контент. Виходять сотні потоків чи процесів, зайнятих лише відправкою даних повільним клієнтам,що створює додаткове навантаження на планувальник ОС, збільшує кількість переривань і споживає багато пам'яті.
  3. Неблоковані сокети/кінцевий автомат. Сервер працює в рамках одного потоку, але використовує неблоковані сокети та механізм полінгу. Тобто. сервер на кожній ітерації нескінченного циклу вибирає з усіх сокетів той, що готовий для прийому/надсилання даних за допомогою виклику select (). Після того, як сокет вибраний, сервер відправляє на нього дані або читає їх, але не чекає підтвердження, а переходить у початковий стан і чекає на події на іншому сокеті або ж обробляє наступний, в якому подія відбулася під час обробки попереднього. Ця модель дуже ефективно використовує процесор та пам'ять, але досить складна у реалізації. Крім того, в рамках цієї моделі обробка події на сокеті повинна відбуватися дуже швидко - інакше в черзі буде накопичуватися багато подій, і врешті-решт вона переповниться. Саме за такою моделлю працює nginx. З іншого боку, дозволяє запускати кілька робочих процесів (так званих workers), тобто. може використовувати кілька процесорів.

Отже, уявимо таку ситуацію: на HTTP-сервер з каналом в 1 Гбіт/с підключається 200 клієнтів з каналом по 256 Кбіт/с:

Що відбувається у випадку Apache? Створюється 200 потоків/процесів, які відносно швидко генерують контент (це можуть бути як динамічні сторінки, так і статичні файли з диска), але повільно віддають його клієнтам. Операційна система змушена справлятися з купою потоків та блокувань введення/виводу. Nginx у такій ситуації витрачає на кожен коннект на порядок менше ресурсів ОС та пам'яті. Однак тут виявляється обмеження мережної моделі nginx: він не може генерувати динамічний контент усередині себе,т.к. це призведе до блокування всередині nginx. Звичайно, рішення є: nginx вміє проксувати такі запити (на генерування контенту) на будь-який інший веб-сервер (наприклад, той самий Apache) або на FastCGI-сервер.

Розглянемо механізм роботи зв'язки nginx як «головного» сервера та Apache як сервер для генерації динамічного контенту:

Nginx приймає з'єднання від клієнта та читає від нього весь запит. Тут слід зазначити, що доки nginx не прочитав весь запит, він не віддає його на обробку. Через це зазвичай "ламаються" практично всі індикатори прогресу закачування файлів - втім, існує можливість відремонтувати їх за допомогою стороннього модуля upload_progress (це вимагатиме модифікації програми). Після того, як nginx прочитав усю відповідь, він відкриває з'єднання до Apache. Останній виконує свою роботу (генерує динамічний контент), після чого віддає свою відповідь nginx, який буферизує його в пам'яті або тимчасовому файлі. Тим часом Apache звільняє ресурси. Далі nginx повільно віддає контент клієнту, витрачаючи при цьому на порядки менше ресурсів, ніж Apache.

Така схема називається фронтенд + бекенд (frontend + backend) і застосовується дуже часто.