# теория · шаг 3 из 5
nginx, Apache, IIS: чем они разные
Коротко
Apache и nginx по-разному отвечают на два вопроса. Соединение: у Apache
запрос, пока он читается, держит процесс или поток, и их конечное число - в
нашем замере восемь медленных клиентов положили сайт с восемью воркерами; у
nginx тысячи соединений живут в одном цикле событий, и семьсот медленных ничего
не меняют. Конфиг: Apache складывает пересекающиеся секции и, если это
разрешено AllowOverride, читает .htaccess на каждом запросе; nginx выбирает
location; наследование зависит от конкретной директивы. 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 | nginx | IIS | |
|---|---|---|---|
| соединение стоит | процесса (prefork) или потока (worker); у event поток нужен, пока запрос читается и обрабатывается |
записи в списке одного воркера | HTTP.sys принимает соединения; потоки обрабатывают запросы |
| конфиг | секции по каталогам, правила складываются | блоки по адресу, побеждает один | графический интерфейс и web.config |
| правила рядом с сайтом | .htaccess, читается на каждый запрос, если включён AllowOverride (по умолчанию выключен) |
нет ничего подобного | web.config в каталоге |
| модули | грузятся из конфига (LoadModule) |
вкомпилированы или динамические .so |
ставятся в систему |
| где живёт | везде | везде | только Windows |
Замер на стенде: у Apache из конфига загружено 10 модулей, у nginx 30 флагов сборки плюс 14 динамических файлов в образе. Отсюда практическое следствие: установленный динамический модуль подключают в конфиге. Если файла модуля нет, сначала нужен подходящий пакет или сборка; это относится и к Apache, и к nginx.
Разбор: восемь медленных клиентов
Стенд: Apache 2.4.68 из образа httpd:alpine с MaxRequestWorkers 8 (в
варианте event - восемь потоков) и умолчаниями mod_reqtimeout, и 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), а их ровно восемь. Девятому клиенту некому ответить, и сайт «лежит», хотя нагрузки нет никакой.
Счётчики процессов под той же нагрузкой:
Apache prefork: 5 процессов в покое -> 9 под нагрузкой
Apache event: 11 потоков, число не меняется
nginx: 2 процесса, число не меняется
Важная тонкость про event, из-за которой его часто переоценивают: он вынес в
отдельный поток простаивающие keepalive-соединения, но не чтение запроса.
Пока заголовки не дочитаны, поток занят - и порог оказался тем же, восьмым.
Эта разница и есть причина, по которой nginx вообще написали: в 1999 году задача называлась «десять тысяч соединений на одной машине», и модель «процесс на соединение» её не решала ни при каком объёме памяти.
Разбор: заголовки на границе каталога
Второе различие видно на одном замысле, записанном дважды: общий заголовок на
сайт плюс свой - для каталога /sub/.
<Directory /www>
Header set X-Level "root"
Header set X-Common "общий"
</Directory>
<Directory /www/sub>
Header set X-Level "sub"
</Directory>
add_header X-Level "root" always;
add_header X-Common "общий" always;
location /sub/ {
add_header X-Level "sub" always;
}
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 сделает с уже лежащим файлом:
код 200, тело: Header set X-From-Htaccess "да"
Файл отдаётся как обычная статика. После переезда со старого хостинга в корне
сайта остаются .htaccess, .env, .git - и всё это открыто, пока не закрыто
явно (двенадцатый раздел). Первое, что делают после переноса, - проверяют
запросом, а не глазами.
Что ломается без этого
Расчёт «клиентов столько же, значит и памяти столько же». У Apache память и
потолок соединений связаны: каждый клиент - процесс или поток со своим стеком,
и MaxRequestWorkers приходится держать таким, чтобы парк влез в оперативную
память. У nginx эта связь разорвана: соединение стоит записи в списке, а память
растёт от работы - от буферов, кеша и тел ответов. Переносить настройки
«в лоб» бессмысленно, у чисел разный смысл.
Надежда на MPM event. Он вынес простаивающие keepalive-соединения в
отдельный поток, и его часто считают ответом на медленных клиентов. Замер
показал обратное: порог остался тем же, восьмым, потому что незаконченный
запрос по-прежнему держит поток. Вывод касается этой конфигурации, а не
всех режимов Apache: медленного клиента рано или поздно выгоняет
RequestReadTimeout из mod_reqtimeout (по умолчанию на заголовки даётся
20-40 секунд), и чем жёстче этот таймаут и больше MaxRequestWorkers, тем выше
порог. Но пока таймаут не истёк, поток занят. Против медленного чтения надёжнее
всего помогает сервер перед приложением - тот самый буфер из первого урока
главы.
.htaccess по привычке. Чтение файла на каждом запросе - не обязательная
плата за Apache: AllowOverride None его отключает, и в стандартном
httpd.conf так и стоит. В замере про переезд его включили, чтобы показать механизм.
Конфиг, переписанный построчно. <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 перепиши правила .htaccess в конфиг nginx и закрой служебные файлы от выдачи. Копирование директив без проверки маршрутов не сохраняет поведение сайта.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий