# теория · шаг 1 из 4
Мастер, воркеры и цикл событий
Коротко
nginx - это один мастер и несколько воркеров, и работу делают только воркеры.
- Мастер не обрабатывает запросы: он читает конфиг, занимает порты 80 и 443 (для них нужен root) и следит за воркерами. Воркеры работают без привилегий.
- Воркер не ждёт конкретного клиента. Он спрашивает у ядра, по каким из его тысяч соединений что-то произошло, и занимается только готовыми.
- Поэтому воркеров нужно примерно столько, сколько ядер: они не ждут, они считают.
- И поэтому одна блокирующая операция внутри воркера останавливает ВСЕ его соединения. Медленный код должен жить в бэкенде, а не в прокси.
Знаешь про цикл событий и epoll - листай до «Теперь сам».
Сколько потоков нужно на двадцать тысяч клиентов
Запусти на сервере с nginx:
ps aux | grep nginx
root 1234 nginx: master process /usr/sbin/nginx
www-data 1235 nginx: worker process
www-data 1236 nginx: worker process
www-data 1237 nginx: worker process
www-data 1238 nginx: worker process
Один процесс от root и четыре от непривилегированного пользователя. Эти
четыре процесса спокойно держат двадцать тысяч одновременных соединений.
Сколько потоков они для этого заводят? Ни одного. Внутри каждого воркера - один поток исполнения, и он ни разу никого не ждёт. Как это возможно - дальше.
На что это похоже
Представь повара, у которого на плите тысяча кастрюль.
Плохой способ: приставить к каждой кастрюле по человеку, и пусть каждый стоит и смотрит, закипела ли. Тысяча человек, каждому нужно место на кухне, и большую часть времени все они просто стоят. Кухня забита людьми, которые ничего не делают.
Хороший способ: один повар, и на каждой кастрюле таймер. Повар не смотрит на кастрюли вообще - он ждёт звонка. Зазвонило три таймера - подошёл, помешал в трёх кастрюлях, вернулся. Тысяча кастрюль, один человек, и он ни секунды не стоит впустую.
Классический сервер - это первый способ, поток на соединение. nginx - второй. Роль таймеров играет ядро операционной системы: оно само следит за всеми сокетами и говорит, по каким из них появились данные.
Механизм: цикл событий
Внутри каждого воркера крутится один и тот же цикл:
бесконечно:
спросить ядро: по каким из моих 5000 соединений что-то произошло?
ядро отвечает: по 17 из них
обработать эти 17, каждое - маленьким кусочком работы
вернуться к началу
Соединение в состоянии «клиент думает» не стоит почти ничего: несколько сотен байт структуры и запись в списке у ядра. Именно поэтому пять тысяч соединений на воркер - нормальная цифра, а не подвиг.
Кто чем занят
Мастер не обрабатывает запросы вообще. Он делает три вещи: читает конфиг,
занимает привилегированные порты (80 и 443 - ниже 1024, для них нужен root) и
следит за воркерами. Умер воркер - мастер поднимет новый. Пришла команда
reload - мастер прочитает конфиг заново и аккуратно заменит воркеров.
Воркеры делают всю работу и работают без привилегий. Если в nginx найдут
дыру, атакующий окажется в процессе от www-data, а не от root. Это не
теория: разделение привилегий - причина, по которой веб-серверы вообще так
устроены.
Правило
Воркеров имеет смысл ровно столько, сколько процессорных ядер. Больше не поможет: воркеры не ждут, они считают, а посчитать быстрее, чем позволяет железо, нельзя.
В воркере не должно быть ничего медленного. Цикл событий один на всё; любая операция, которая заблокирует его на 10 миллисекунд, заблокирует на эти 10 миллисекунд все пять тысяч соединений.
Разбор: почему поток на соединение не масштабируется
Сравним два способа держать 10 000 соединений на одной машине.
Поток на соединение. Стек потока в Linux по умолчанию - 8 МБ адресного пространства:
10 000 × 8 МБ = 80 ГБ виртуальной памяти
Физически будет занято меньше - страницы выделяются по мере использования, - но остаётся вторая цена, и она хуже. Планировщик ядра обязан по очереди раздавать процессорное время десяти тысячам потоков; каждое переключение контекста стоит порядка микросекунды плюс промахи кеша. При сколько-нибудь живом трафике машина начинает тратить на переключения больше, чем на работу.
Цикл событий. Четыре процесса, четыре стека. Каждое соединение - структура в памяти и запись в списке ядра, сотни байт. Переключений контекста между соединениями нет вовсе: это не разные потоки, это ветки одного цикла.
Отсюда и цифры из начала урока: четыре процесса, двадцать тысяч клиентов, никакого героизма.
Разбор: где модель ломается
Из первого разбора легко унести «цикл событий всегда лучше». Вот случай, где он подводит, и подводит громко.
Воркер держит 4096 соединений. Приходит запрос на файл, которого нет в дисковом кеше. Обычное чтение с диска - операция блокирующая: воркер уходит в неё и не возвращается, пока диск не ответит. На вращающемся диске это около 10 мс.
10 мс блокировки × 100 таких запросов в секунду = 1 секунда простоя в секунду
То есть при сотне промахов кеша в секунду воркер занят ожиданием диска постоянно, и все 4096 его соединений стоят вместе с ним. Снаружи это выглядит как «сайт периодически подвисает целиком», причём процессор снова свободен.
Лечится выносом чтения в отдельный пул потоков:
aio threads;
Теперь блокирующее чтение уходит в пул, а цикл событий продолжает крутиться. Это же объясняет, почему nginx принципиально не хочет выполнять твой код в своём процессе: любой пользовательский код рано или поздно во что-нибудь заблокируется, и заплатят за это все соединения воркера сразу.
Что ломается без этого
«Сайт замирает на секунду-другую, потом отпускает». Классическая блокировка воркера: медленный диск, сетевая файловая система, тяжёлый модуль. Процессор при этом свободен.
«Поставил worker_processes 64 - лучше не стало». Воркеры не ждут, им нечего параллелить сверх числа ядер. Стало даже хуже: 64 процесса дерутся за 8 ядер.
«nginx работает от root». Значит воркеры тоже, и любая дыра в модуле сразу
даёт root. Проверяется тем самым ps aux: у воркеров обязан стоять
непривилегированный пользователь.
Зачем это в работе
Первое - разбор подвисаний. Когда сайт замирает целиком и одновременно, а процессор свободен, круг подозреваемых сразу сужается до блокировок в воркере: диск, DNS-резолв в рантайме, сторонний модуль. Без модели процессов на это уходит вечер, с моделью - десять минут.
Второе - разговор о безопасности. Вопрос «под кем работает nginx» имеет
ровно один правильный ответ, и ps aux показывает его за секунду.
Теперь сам
На сервере 8 ядер. Администратор увидел, что nginx упирается в производительность,
и поставил worker_processes 32, а заодно worker_connections 65535. Что из
этого поможет, что навредит и чего он, скорее всего, не проверил?
Ответ. worker_processes 32 навредит: воркеры не ждут, и 32 процесса будут
конкурировать за 8 ядер, добавив переключений контекста. Правильное значение -
auto, то есть 8. worker_connections 65535 само по себе не вредит, но
бесполезно, если не поднят лимит открытых файлов процесса - тогда в логе
появится worker_connections exceed open file resource limit. А не проверил он,
скорее всего, главное: упёрлось ли вообще что-нибудь. Если процессор свободен, а
сайт медленный, дело не в числе воркеров, а в блокировке внутри них или в
бэкенде - и тюнинг этих двух чисел не даст ничего.
Главное
Мастер держит привилегии и конфиг, воркеры без привилегий обрабатывают запросы. Воркер не ждёт клиентов, а спрашивает у ядра, по каким соединениям что-то произошло, - отсюда тысячи соединений на процесс и правило «воркеров столько, сколько ядер». Обратная сторона той же модели: одна блокирующая операция останавливает все соединения воркера сразу.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий