1. 1
  2. 2
  3. 3
  4. 4
  5. 5

# теория · шаг 3 из 5

nginx, Apache, IIS: чем они разные

Коротко

Apache и nginx по-разному отвечают на два вопроса. Соединение: у Apache его обслуживает процесс или поток, и их конечное число - восемь медленных клиентов кладут сайт с восемью воркерами; у nginx тысячи соединений живут в одном цикле событий, и семьсот медленных ничего не меняют. Конфиг: Apache складывает пересекающиеся секции и читает .htaccess на каждом запросе, nginx выбирает один блок и заменяет им унаследованное. IIS - отдельный мир Windows и .NET.

Если с Apache дела не имел - тебе хватит «Правило» и последнего блока про выбор.

Восемь клиентов с плохим интернетом. Не атака, не нагрузка - просто восемь человек, у которых запрос идёт по сети медленно. Что станет с сайтом?

↳  выводстенд: медленные клиенты против одного обычного запроса
Apache prefork (8 воркеров):   не ответил, таймаут 5 с
Apache event   (8 потоков):    не ответил, таймаут 5 с
nginx (1 воркер):              ответ за 0,001 с

У nginx тот же замер повторён на семистах медленных соединениях - ответ всё те же 0,001 секунды, и в системе по-прежнему два процесса.

На что это похоже

Apache в классическом виде - это касса на посетителя: пришёл человек - открыли кассу, ушёл - закрыли. Пока он ищет кошелёк, касса занята и никого больше не обслуживает. Касс столько, сколько разрешил владелец; кончились - очередь стоит на улице.

nginx - один кассир, который никогда никого не ждёт. Пока посетитель ищет кошелёк, кассир пробивает следующего, а к первому вернётся, когда тот будет готов. Отсюда и разница: у Apache потолок - число касс, у nginx - объём работы, а не число людей в зале.

Вторая разница - в конфиге, и она устроена так же зеркально: Apache читает правила по дороге и складывает их, nginx выбирает одно и работает по нему.

Механизм: две модели обслуживания

Apache: касса на клиента клиент 1 процесс 1 клиент 2 процесс 2 клиент 9 касс нет потолок - число процессов nginx: один цикл тысячи соединений воркер потолок - объём работы медленный клиент занимает кассу, но не занимает цикл
Соединение у Apache стоит процесса или потока, у nginx - записи в списке

Правило

Apache nginx IIS
соединение стоит процесса (prefork) или потока (worker, event) записи в списке одного воркера потока из пула, очередь держит HTTP.sys
конфиг секции по каталогам, правила складываются блоки по адресу, побеждает один графический интерфейс и web.config
правила рядом с сайтом .htaccess, читается на каждый запрос нет ничего подобного web.config в каталоге
модули грузятся из конфига (LoadModule) вкомпилированы или динамические .so ставятся в систему
где живёт везде везде только Windows

Замер на стенде: у Apache из конфига загружено 10 модулей, у nginx 30 флагов сборки плюс 14 динамических файлов в образе. Отсюда практическое следствие из третьего раздела курса: у Apache «модуля нет» лечится строкой в конфиге, у nginx - установкой пакета или пересборкой.

Разбор: восемь медленных клиентов

Стенд: Apache 2.4.68 с MaxRequestWorkers 8 (в варианте event - восемь потоков) и nginx с одним воркером и worker_connections 1024. Клиент подключается и отправляет заголовки, но не дописывает их до конца - обычное поведение мобильной сети, а не атака. Параллельно идёт один нормальный запрос.

медленных клиентов Apache prefork Apache event nginx
4 0,002 с 0,002 с 0,001 с
7 1,567 с 0,001 с -
8 не ответил не ответил 0,001 с
100 - - 0,001 с
700 - - 0,001 с

Обрыв ровно на восьмой связи - это и есть модель: незаконченный запрос держит процесс (prefork) или поток (event), а их ровно восемь. Девятому клиенту некому ответить, и сайт «лежит», хотя нагрузки нет никакой.

Счётчики процессов под той же нагрузкой:

↳  выводсводка по docker top при 30 медленных соединениях
Apache prefork:  5 процессов в покое -> 9 под нагрузкой
Apache event:    11 потоков, число не меняется
nginx:           2 процесса, число не меняется

Важная тонкость про event, из-за которой его часто переоценивают: он вынес в отдельный поток простаивающие keepalive-соединения, но не чтение запроса. Пока заголовки не дочитаны, поток занят - и порог оказался тем же, восьмым.

Эта разница и есть причина, по которой nginx вообще написали: в 1999 году задача называлась «десять тысяч соединений на одной машине», и модель «процесс на соединение» её не решала ни при каком объёме памяти.

Разбор: заголовки на границе каталога

Второе различие видно на одном замысле, записанном дважды: общий заголовок на сайт плюс свой - для каталога /sub/.

/usr/local/apache2/conf/httpd.conf
<Directory /www>
    Header set X-Level "root"
    Header set X-Common "общий"
</Directory>
<Directory /www/sub>
    Header set X-Level "sub"
</Directory>
/etc/nginx/conf.d/lab.confhttp server
add_header X-Level  "root"  always;
add_header X-Common "общий" always;

location /sub/ {
    add_header X-Level "sub" always;
}
↳  выводзаголовки ответа на /sub/
Apache:  X-Level: sub    X-Common: общий
nginx:   X-Level: sub

Apache для /sub/ собрал правила из обеих секций: свой X-Level перекрыл общий, а X-Common остался. nginx выбрал один блок - location /sub/, - и его add_header заменил унаследованный набор целиком.

Это не частный случай заголовков, а общее правило nginx, которое встретится ещё не раз: наследуется целиком или никак. Так же ведут себя proxy_set_header и списки allow/deny.

Практический вывод для переезда: каждый блок, где появился свой add_header, обязан повторить общие заголовки - либо один раз написать add_header_inherit merge; (nginx 1.29.3), и тогда поведение станет ближе к привычному по Apache.

Разбор: .htaccess после переезда

На Apache файл в каталоге меняет поведение без перезагрузки - замер: положили .htaccess с одной строкой, следующий же ответ пришёл с новым заголовком. Это удобство и есть причина, по которой хостинги дают .htaccess вместо доступа к конфигу.

У nginx такого механизма нет вовсе, и это осознанное решение: за него платят чтением файлов на каждом запросе по всему пути каталогов. Но опасность другая - что nginx сделает с уже лежащим файлом:

↳  выводcurl http://сайт/sub/.htaccess на nginx
код 200, тело: Header set X-From-Htaccess "да"

Файл отдаётся как обычная статика. После переезда со старого хостинга в корне сайта остаются .htaccess, .env, .git - и всё это открыто, пока не закрыто явно (двенадцатый раздел). Первое, что делают после переноса, - проверяют запросом, а не глазами.

Что ломается без этого

Расчёт «клиентов столько же, значит и памяти столько же». У Apache память и потолок соединений связаны: каждый клиент - процесс или поток со своим стеком, и MaxRequestWorkers приходится держать таким, чтобы парк влез в оперативную память. У nginx эта связь разорвана: соединение стоит записи в списке, а память растёт от работы - от буферов, кеша и тел ответов. Переносить настройки «в лоб» бессмысленно, у чисел разный смысл.

Надежда на MPM event. Он вынес простаивающие keepalive-соединения в отдельный поток, и его часто считают ответом на медленных клиентов. Замер показал обратное: порог остался тем же, восьмым, потому что незаконченный запрос по-прежнему держит поток. Против медленного чтения помогает не смена MPM, а сервер перед приложением - тот самый буфер из первого урока главы.

Конфиг, переписанный построчно. <Directory> и location похожи ровно до первого пересечения: в Apache секции складываются в порядке от общего к частному, в nginx выигрывает один блок по своим правилам приоритета (четвёртый раздел). Правило из Apache «более частная секция дополняет общую» здесь не работает.

Ожидание, что «положу файл - и подействует». Всё, что в Apache решалось .htaccess, в nginx решается конфигом и перезагрузкой. Для сайтов, которые раздают пользователям каталоги, это меняет схему работы целиком.

Переезд с IIS. Там кроме конфига есть привязка к системе: аутентификация Windows, приложения на .NET в пуле приложений, права через ACL файловой системы. nginx перед .NET-приложением ставят, но заменять им IIS целиком означает переносить и то, что жило в интеграции с доменом.

Зачем это в работе

Короткий ответ на вопрос «что выбрать»:

  • nginx - когда нужен фронт: статика, TLS, балансировка, лимиты, кеш. Ниша, ради которой он и написан, - много одновременных соединений при малой памяти.
  • Apache - когда нужен .htaccess для чужих сайтов (классический хостинг) или модуль, которого больше нигде нет; mod_php внутри процесса сегодня почти везде заменён на php-fpmРазбирается в разделе 7, глава «PHP и FastCGI»: Как nginx разговаривает с PHP - отдельную службу для PHP, - к которой одинаково ходят оба сервера.
  • IIS - когда стек Windows: .NET, аутентификация домена, управление через графический интерфейс.
  • Caddy - когда хочется сертификаты «из коробки» и конфиг в пять строк; цена - меньше рычагов, когда понадобится тонко.
  • HAProxy, Envoy, Traefik - это уже не веб-серверы, а балансировщики и маршрутизаторы: статику они не отдают, зато у них своё в наблюдаемости и динамической конфигурации.

Связка «nginx впереди, Apache сзади» - до сих пор живая: nginx держит соединения и отдаёт статику, Apache исполняет старое приложение с его модулями. И знание фаз nginx помогает читать чужие конфиги в обе стороны.

Теперь сам

Сайт на Apache prefork с MaxRequestWorkers 150 вечерами перестаёт отвечать, хотя процессор загружен на десять процентов, а в access_log в эти минуты почти пусто. Коллега предлагает поднять MaxRequestWorkers до 500. Что тут не так и куда смотреть?

Ответ: пустой access_log - главная улика. Строка пишется, когда запрос завершён; если соединения висят на медленном чтении, они держат процессы и в логе не появляются вовсе. Загруженность процессора при этом низкая - работы нет, есть ожидание. Поднимать MaxRequestWorkers опасно: каждый процесс prefork несёт свою память (а с mod_php - ещё и интерпретатор), и 500 процессов кончатся не отказом в обслуживании, а свопом или OOM. Смотреть надо на число занятых воркеров против числа завершённых запросов (mod_status), а лечится это не числом касс, а тем, чтобы медленных клиентов принимал кто-то другой: nginx впереди дочитывает запрос целиком и отдаёт бэкенду готовый.

Главное

Главное различие - в обслуживании соединения. У Apache оно стоит процесса (prefork) или потока (worker, event), и потолок жёсткий: замер показал обрыв ровно на восьмом медленном клиенте при восьми воркерах, причём event не спас - он вынес в отдельный поток простаивающие keepalive, но не чтение запроса. У nginx соединение стоит записи в списке одного воркера: семьсот медленных клиентов не изменили ни времени ответа, ни числа процессов. Второе различие - конфиг: Apache складывает пересекающиеся секции по каталогам и читает .htaccess на каждом запросе, nginx выбирает один блок и заменяет унаследованное (замер: /sub/ получил от Apache и свой заголовок, и общий, а от nginx - только свой). Оставшийся после переезда .htaccess nginx отдаёт как статику с кодом 200. IIS - мир Windows и .NET; Caddy - простота и автоматические сертификаты; HAProxy, Envoy и Traefik - балансировщики, а не веб-серверы.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.