Архитектура процессов: master, worker и event loop

Рейтинг: 80.6% · 16 голосов
Самый подробный курс по Nginx - от первого конфига до production API-gateway. Архитектура и event loop, контексты и location, виртуальные хосты, статика, reverse proxy и upstream-балансировка, кэширование, HTTPS и TLS (post-quantum), HTTP/2 и HTTP/3 QUIC, безопасность и rate limiting, realip, JSON-логи и сквозная трассировка, метрики, тюнинг и траблшутинг. Плюс OpenResty (Lua, фазы, JWT, динамика) и Angie (нативные Prometheus, ACME, dynamic upstream). На основе эталонного gateway и лучших практик. Для новичков и SRE/DevOps. Актуально на 2026 (nginx 1.30/1.31, Angie, ingress-nginx EOL -> Gateway API).
Ответить
Аватара пользователя
Igor_NgINX
Сообщения: 44
Зарегистрирован: 11 май 2026, 05:31

Архитектура процессов: master, worker и event loop

Сообщение Igor_NgINX »

Оглавление курса (44)
  1. Что такое Nginx и почему он захватил веб
  2. Установка Nginx, Angie и OpenResty: пакеты, версии, Docker
  3. Первый конфиг с нуля: минимальный сервер за 5 минут
  4. Архитектура процессов: master, worker и event loop (вы здесь)
  5. Анатомия конфигурации: контексты, наследование и модульность
  6. Виртуальные хосты: server и server_name
  7. Директива location: матчинг и приоритеты по шагам
  8. Раздача статики: root, alias, try_files, sendfile
  9. Переменные, map и блок if
  10. rewrite, return и канонизация URL
  11. Работа с заголовками: add_header и proxy_set_header
  12. Модель доверия прокси-заголовков: realip и защита от спуфинга
  13. Сжатие: gzip, brotli и zstd
  14. Reverse proxy: proxy_pass и проброс запроса
  15. upstream и балансировка нагрузки
  16. Таймауты, повторы и устойчивость прокси
  17. FastCGI и PHP: nginx + PHP-FPM (и uwsgi/scgi)
  18. WebSocket, gRPC и стриминг через Nginx
  19. Кэширование ответов: proxy_cache и микрокэш
  20. HTTPS и TLS: сертификаты, протоколы, шифры (2026)
  21. Let's Encrypt и ACME: certbot, acme.sh, нативный ACME Angie
  22. HTTP/2 и HTTP/3 (QUIC) в 2026
  23. Безопасность: заголовки, скрытие версии, ограничения
  24. Rate limiting и защита от перегрузки
  25. Контроль доступа: allow/deny, auth_basic, auth_request и mTLS
  26. Логирование: access_log, форматы и структурированный JSON
  27. Сквозная трассировка запросов и OpenTelemetry
  28. Метрики и мониторинг Nginx
  29. Производительность и тюнинг под нагрузку
  30. Траблшутинг: 502, 504, 403, 404 и debug-лог
  31. OpenResty: Nginx как платформа на LuaJIT
  32. Фазы обработки запроса и Lua-хуки
  33. Lua API: ngx.*, shared dict, cosocket и lua-resty-core
  34. Практика OpenResty: авторизация, JWT, кэш, Redis
  35. Angie: современный форк Nginx 2026
  36. Динамическая конфигурация и service discovery
  37. Nginx как API-gateway и Kubernetes Gateway API
  38. Динамические модули, njs и WAF
  39. Stream-модуль: проксирование TCP и UDP
  40. Деплой, перезагрузка и конфиг как код
  41. Сценарий: статика, SPA и кэширование за CDN
  42. Сценарий: микросервисный gateway по образцу ngx-trace-gateway
  43. Капстоун: собираем production-gateway с нуля
  44. Чек-лист безопасности, CVE 2026 и аудит конфигурации
Зачем вообще одному веб-серверу несколько процессов

Представь старый Apache prefork: пришёл клиент - выделили процесс. Пришла тысяча клиентов - тысяча процессов, каждый со своим стеком, своим контекстом, своим куском памяти. Ядро мечется между ними, переключая контекст десятки тысяч раз в секунду, и большую часть времени эти процессы просто СПЯТ, ожидая, пока клиент дошлёт байты по медленному мобильному каналу. Память кончается, планировщик задыхается, сервер падает на нескольких тысячах соединений. Это и есть та боль, ради которой родился nginx.

Архитектура nginx устроена принципиально иначе. Тут не "один клиент - один поток", а горстка процессов-воркеров, каждый из которых неблокирующе жонглирует тысячами соединений одновременно. Один воркер на одно ядро CPU, никакой возни с тредами на каждый запрос, минимум переключений контекста. Именно поэтому связка master worker nginx спокойно тянет десятки тысяч одновременных соединений на железе, на котором тредовый сервер захлебнулся бы. В этом уроке разберём по косточкам: кто такой master, кто такой worker, как внутри воркера крутится event loop, и почему все вместе это масштабируется.

Изображение

Master и worker: кто за что отвечает

После старта ты увидишь два сорта процессов. Master (главный) - один. Он НЕ обрабатывает клиентские запросы вообще. Его задачи привилегированные и редкие: прочитать и распарсить конфиг, забиндить слушающие сокеты на порты 80/443 (для этого нужен root, привязка к портам ниже 1024), а затем породить воркеров и присматривать за ними. Если воркер упал - master поднимет новый. Master читает твои сигналы (reload, остановка) и дирижирует.

Worker (рабочий) - их обычно несколько. Вот они и делают всю работу: принимают соединения с уже открытых master-ом сокетов, читают запросы, проксируют на бэкенд, отдают ответы, пишут логи. Воркеры запускаются под непривилегированным пользователем (обычно nginx или www-data) - даже если в воркере найдут дыру, атакующий не получит root. Слушающие сокеты унаследованы от master через fork(), поэтому воркеру уже не нужны привилегии, чтобы принимать на них соединения.

Посмотрим на реальный nginx процессы вывод. Запусти ps и сам увидишь иерархию:

Код: Выделить всё

$ ps -eo pid,ppid,user,%cpu,rss,cmd --sort=ppid | grep -E 'nginx' | grep -v grep
   1   0 root     0.0  6400 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
 842   1 nginx    0.3 12800 nginx: worker process
 843   1 nginx    0.4 13056 nginx: worker process
 844   1 nginx    0.2 12544 nginx: worker process
 845   1 nginx    0.5 13312 nginx: worker process
 846   1 nginx    0.0  5120 nginx: cache manager process
Разбор. PID 1 - master, владелец root, PPID 0. Ниже четыре воркера, у всех PPID 842... нет, PPID 1 - то есть их родитель именно master. User у них nginx, не root - так и должно быть. RSS воркеров около 12-13 МБ - это вся память на сотни активных соединений в каждом, а не по мегабайту на клиента. И отдельный cache manager - вспомогательный процесс, он чистит дисковый кэш по inactive/max_size, к обработке запросов отношения не имеет. Если включён proxy_cache, ты увидишь ещё и cache loader при старте.

Сколько воркеров и сколько соединений: worker_processes и worker_connections

Главная директива - nginx worker_processes. Она задаёт, сколько рабочих процессов поднять. Канонический ответ на 2026 год:

Код: Выделить всё

# в корне nginx.conf, вне http{}
worker_processes auto;
Значение auto заставляет nginx определить число доступных ядер CPU и запустить ровно столько воркеров. Почему один воркер на ядро, а не десять? Потому что воркер неблокирующий - он не простаивает в ожидании сети, он всегда занят полезной работой, пока есть готовые события. Дать воркеров больше, чем ядер, бессмысленно: они начнут драться за одни и те же ядра, добавляя переключения контекста, ровно ту болезнь, от которой мы убегали. В эталонном gate-проекте именно так: nginx.conf начинается с worker_processes auto. Жёстко прибивать число (worker_processes 4) имеет смысл, только если ты вручную пинишь воркеры к ядрам через worker_cpu_affinity или делишь машину с другими сервисами.

Вторая директива живёт уже внутри блока events и отвечает на вопрос "сколько соединений тянет ОДИН воркер":

Код: Выделить всё

events {
    worker_connections 10240;
    multi_accept on;
    use epoll;
}
nginx worker_connections - это не клиенты, а соединения вообще, включая исходящие к апстримам, к резолверу, служебные. Эталонный проект ставит 10240 - щедрое, осознанное значение для шлюза. Теперь арифметика, которую обязан знать каждый. Теоретический потолок одновременных соединений:

Код: Выделить всё

max_connections = worker_processes * worker_connections
# например 4 * 10240 = 40960
Но есть ловушка для прокси. Когда nginx работает реверс-прокси, каждый клиентский запрос съедает ДВА слота: одно соединение от клиента к nginx и одно от nginx к бэкенду. Поэтому реальный потолок клиентов для proxy примерно (worker_processes * worker_connections) / 2. Для статики делитель не нужен - там одно соединение на клиента.

И ещё одна обязательная связка. Каждое соединение в Linux - это файловый дескриптор. Если worker_connections 10240, а лимит дескрипторов на процесс 1024 (дефолт многих систем), воркер упрётся в потолок ОС задолго до своего номинала, и в логах посыплется "Too many open files". Лечится директивой:

Код: Выделить всё

worker_rlimit_nofile 65535;
Она поднимает лимит дескрипторов (RLIMIT_NOFILE) для воркеров через setrlimit, и делать это надо прямо в nginx.conf, потому что ulimit в шелле на демона, запущенный systemd, не влияет. Эмпирика: worker_rlimit_nofile должен быть хотя бы вдвое больше worker_connections (соединение плюс возможный апстрим-сокет плюс открытые файлы).

Event loop вглубь: epoll, kqueue и почему это масштабируется

Вот сердце всего. nginx event loop - это бесконечный цикл внутри каждого воркера, который вместо "жди на этом сокете" говорит ядру "разбуди меня, когда ХОТЬ ЧТО-ТО из тысяч моих сокетов будет готово". Механизм этого опроса - мультиплексор событий ОС.

На Linux это epoll (директива use epoll, как в эталоне). На FreeBSD и macOS - kqueue. На старых системах был медленный select/poll, но их давно не используют. Современный nginx и сам выбирает лучший доступный метод, но явный use epoll в events делает намерение читаемым и убирает любые сомнения.

В чём фокус epoll. Старый select работал за O(n): чтобы узнать, какой из тысячи сокетов готов, ядро каждый раз линейно перебирало весь список. На 10 тысячах соединений это смерть. epoll работает за O(1) относительно числа простаивающих соединений: ты один раз регистрируешь сокеты через epoll_ctl, а потом epoll_wait возвращает только те, на которых РЕАЛЬНО что-то случилось. Тысячи дремлющих keepalive-соединений не стоят почти ничего - они просто висят в ядре и не отъедают CPU.

Псевдокод того, что крутится в воркере, помогает увидеть суть:

Код: Выделить всё

// упрощённая модель event loop одного воркера
for (;;) {
    events = epoll_wait(epfd, ...);     // спим, пока ядро не разбудит
    for (e in events) {
        if (e.listen_ready)  accept_new_connection();   // новый клиент
        if (e.read_ready)    read_request_nonblocking(); // байты пришли
        if (e.write_ready)   send_response_nonblocking();// можно слать
        if (e.timer_expired) handle_timeout();           // таймаут
    }
    process_timers();   // proxy_read_timeout, keepalive_timeout и т.п.
}
Ключевое слово - nonblocking. Воркер НИКОГДА не зависает на одном клиенте. Прочитал доступные байты - вернулся в цикл. Бэкенд тормозит с ответом? Воркер не ждёт - он обслуживает остальные 9999 соединений, а к этому вернётся, когда epoll скажет "готово". Вот почему один воркер заменяет тысячу тредов: нет тысячи стеков, нет тысячи переключений контекста, вся машина состояний каждого соединения - это компактная структура в памяти воркера. Цена соединения - килобайты, а не мегабайты.

multi_accept on (тоже из эталона) меняет поведение при пробуждении: без неё воркер за один проход цикла принимает одно новое соединение, с ней - принимает в цикле ВСЕ ожидающие в очереди accept, пока она не опустеет. На всплесках подключений это снижает число итераций event loop и уменьшает задержку приёма. Для нагруженного шлюза - разумный дефолт.

Осталось разобраться, как несколько воркеров делят ОДИН слушающий сокет, не передравшись. Тут два механизма. Старый - accept_mutex: воркеры по очереди берут мьютекс, и только держатель принимает новые соединения; это лечило "громовое стадо" (thundering herd), когда новое соединение будило ВСЕ воркеры разом, а доставалось одному. На 2026 год accept_mutex по умолчанию OFF, потому что современные ядра дают флаг EPOLLEXCLUSIVE: ядро само будит только один воркер, мьютекс в userspace больше не нужен.

Новый и более мощный механизм - SO_REUSEPORT через параметр reuseport:

Код: Выделить всё

http {
    server {
        listen 443 ssl reuseport;   # reuseport ставится ОДИН раз на сокет
        http2 on;
        # ...
    }
}
С reuseport ядро создаёт ОТДЕЛЬНУЮ очередь accept для каждого воркера и само раскидывает входящие соединения по ним хэшем. Никакого общего сокета, никакой борьбы за мьютекс - каждый воркер пасётся на своей грядке. Включение reuseport автоматически отключает accept_mutex для этого сокета (он становится не нужен). На многоядерных машинах под высокой нагрузкой это заметно ровнее распределяет соединения и срезает latency. Важно: reuseport пишут ОДИН раз на адрес:порт, не дублируй его в каждом server, иначе получишь конфликт.

Graceful reload: как менять конфиг без обрыва соединений

Самая ценная практическая суперсила этой архитектуры - перечитать конфиг без единого сброшенного запроса. Команда:

Код: Выделить всё

$ nginx -t && nginx -s reload
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Сначала ОБЯЗАТЕЛЬНО nginx -t - проверка синтаксиса на сухую. Потом reload, который под капотом шлёт master-у сигнал HUP. Что происходит дальше, по шагам:
  • master перечитывает и валидирует новый конфиг; если там ошибка - НИЧЕГО не меняется, старые воркеры работают как ни в чём не бывало, ты просто получишь ошибку в stderr/логе;
  • если конфиг валиден, master заново биндит сокеты (если порты сменились) и порождает НОВЫЕ воркеры уже с новой конфигурацией;
  • новым воркерам master отдаёт все свежие входящие соединения;
  • СТАРЫМ воркерам master шлёт сигнал на изящное завершение - они перестают принимать новые соединения, но ДОРАБАТЫВАЮТ уже начатые запросы до конца;
  • как только старый воркер дообслужил последнего клиента, он сам тихо умирает.
Поэтому в момент reload в ps на пару секунд ты увидишь старые и новые воркеры одновременно - старые помечены как "shutting down":

Код: Выделить всё

$ ps -eo pid,ppid,user,cmd | grep nginx | grep -v grep
   1   0 root  nginx: master process /usr/sbin/nginx
 842   1 nginx nginx: worker process is shutting down
 843   1 nginx nginx: worker process is shutting down
1501   1 nginx nginx: worker process
1502   1 nginx nginx: worker process
Долгие соединения (например WebSocket или висящий стрим) могут держать старый воркер живым надолго - его поведение управляется директивой worker_shutdown_timeout, по истечении которой такие соединения принудительно рвутся. Запомни: reload - это НЕ restart. Restart (systemctl restart) убивает процесс целиком и поднимает заново - в окно перезапуска соединения рвутся. Reload бесшовен. В нагруженном проде делай reload.

Сигналы управления: HUP, USR1, USR2, QUIT

Под красивыми командами nginx -s ... лежат обычные unix-сигналы master-у. Знать их полезно, потому что в скриптах и при отладке ты будешь слать их напрямую через kill:
  • HUP - reload конфига (то самое graceful, разобрано выше). Эквивалент nginx -s reload;
  • USR1 - переоткрыть лог-файлы. Главный сценарий: ротация логов. logrotate переименовал access.log в access.log.1, ты шлёшь USR1, nginx закрывает старые дескрипторы и открывает файлы заново по их именам - без этого он продолжал бы писать в уже переименованный файл по старому inode. Эквивалент nginx -s reopen;
  • USR2 - горячее обновление бинарника nginx без обрыва соединений. Master форкает новый master из нового бинарника, оба работают параллельно, потом старый гасится. Так апгрейдят сам nginx на лету;
  • QUIT - изящная остановка: воркеры дорабатывают запросы и завершаются. Эквивалент nginx -s quit. Его злой брат TERM/INT - это быстрая остановка с обрывом всего здесь и сейчас (nginx -s stop).
Пример прямой отправки сигнала, когда под рукой только PID master:

Код: Выделить всё

$ cat /run/nginx.pid
1
$ kill -HUP $(cat /run/nginx.pid)   # то же, что nginx -s reload
$ kill -USR1 1                       # переоткрыть логи после ротации
Типичные грабли
  • worker_connections без worker_rlimit_nofile. Ставят 10240 соединений, а лимит дескрипторов ОС остаётся 1024 - и сервер ложится с "Too many open files" задолго до проектной нагрузки. Всегда поднимай worker_rlimit_nofile вместе.
  • Считают worker_connections как число клиентов на проксе. Забывают про второй сокет к апстриму и удивляются, почему ёмкость вдвое меньше ожидаемой. На реверс-прокси дели потолок на два.
  • worker_processes больше числа ядер "чтобы быстрее". Эффект обратный: воркеры конкурируют за CPU, растут переключения контекста. auto почти всегда правильный ответ.
  • restart вместо reload в проде. Рвут живые соединения там, где хватило бы бесшовного HUP. Restart нужен только когда меняешь то, что нельзя подхватить на лету (например worker_processes на некоторых сборках или загруженные модули).
  • reload без nginx -t. Если новый конфиг битый, на reload master его отвергнет и продолжит на старом - но если ты вместо reload сделал restart с битым конфигом, nginx просто не поднимется и ляжет весь сайт. Привычка nginx -t && nginx -s reload спасает.
  • reuseport в каждом server-блоке. Параметр на адрес:порт ставится РОВНО один раз, дубли дают ошибку запуска.
Мини-лаба: пощупать архитектуру руками

Повтори по шагам на любой тестовой машине с nginx (можно в Docker, образ как в эталоне - openresty/openresty:latest или nginx:stable).
  • Шаг 1. Запусти nginx и посмотри иерархию:

    Код: Выделить всё

    ps -eo pid,ppid,user,cmd | grep nginx | grep -v grep
    Найди master (root, PPID других процессов) и воркеры (непривилегированный user, PPID = pid master). Запиши, сколько воркеров - оно должно совпасть с числом ядер (nproc).
  • Шаг 2. Открой nginx.conf, поставь явно

    Код: Выделить всё

    worker_processes 2;
    и в events

    Код: Выделить всё

    worker_connections 4096; use epoll; multi_accept on;
    Проверь конфиг:

    Код: Выделить всё

    nginx -t
  • Шаг 3. Перечитай конфиг бесшовно:

    Код: Выделить всё

    nginx -s reload
    Тут же снова сделай ps. Если успеешь в первую секунду - поймаешь старые воркеры со статусом "is shutting down" рядом с новыми. Убедись, что теперь воркеров ровно два.
  • Шаг 4. Проверь лимит дескрипторов воркера: возьми PID любого воркера и глянь

    Код: Выделить всё

    cat /proc/<PID>/limits | grep "open files"
    Добавь в nginx.conf

    Код: Выделить всё

    worker_rlimit_nofile 65535;
    сделай reload и убедись, что Max open files у нового воркера вырос.
  • Шаг 5. Симулируй ротацию логов: переименуй access.log, пошли

    Код: Выделить всё

    nginx -s reopen
    (это USR1) и проверь, что nginx создал новый access.log и пишет в него.
Поздравляю - ты только что вживую увидел master/worker, graceful reload и сигналы, на которых стоит вся эксплуатация nginx.

Контрольные вопросы
  • Почему один воркер на event loop с epoll обслуживает тысячи соединений эффективнее, чем тысяча потоков, каждый на своём соединении? В чём именно экономия?
  • У тебя worker_processes 4 и worker_connections 10240. Сколько примерно ОДНОВРЕМЕННЫХ клиентов выдержит сервер, если он работает реверс-прокси, и почему не 40960?
  • Чем graceful reload (HUP) отличается от restart с точки зрения активных соединений? Что произойдёт, если новый конфиг при reload окажется синтаксически битым?
  • Зачем нужна worker_rlimit_nofile и что случится, если задрать worker_connections, забыв про неё?
Итог

Архитектура nginx держится на трёх китах. Master - привилегированный дирижёр: читает конфиг, биндит порты, рулит воркерами и обрабатывает сигналы, но сам запросы не трогает. Worker - неблокирующая рабочая лошадка: один на ядро (worker_processes auto), каждый через event loop на epoll/kqueue (use epoll, multi_accept on) тянет тысячи соединений практически бесплатно по памяти и CPU. Потолок ёмкости - worker_processes умножить на worker_connections, не забывая делить на два для прокси и поднимать worker_rlimit_nofile под число дескрипторов. И венчает всё graceful reload по сигналу HUP: меняешь конфиг без единого оборванного запроса, потому что старые воркеры дорабатывают своё, а новые принимают свежий трафик. Это фундамент - дальше мы будем строить на нём location-роутинг, проксирование и кэш.
👍2 ❤️3 🔥3 😄 🤔1
Аватара пользователя
bun2
Сообщения: 1
Зарегистрирован: 24 май 2026, 01:44

Re: Архитектура процессов: master, worker и event loop

Сообщение bun2 »

А worker_shutdown_timeout по дефолту какой? У меня вебсокеты, боюсь что старые воркеры после reload будут висеть вечно и накапливаться. Стоит его руками выставлять или nginx сам разрулит?
👍 ❤️ 🔥 😄 🤔1
Аватара пользователя
netru2_silver
Сообщения: 1
Зарегистрирован: 11 май 2026, 17:55

Re: Архитектура процессов: master, worker и event loop

Сообщение netru2_silver »

Долго не мог понять, почему worker_connections 10240, а реально клиентов влезает вдвое меньше. Дошло только тут - на проксе же два сокета на запрос, клиентский и к бэкенду. Спасибо, перестал гадать.
👍 ❤️2 🔥1 😄 🤔
Ответить
← Предыдущая глава
Первый конфиг с нуля: минимальный сервер за 5 минут
Следующая глава →
Анатомия конфигурации: контексты, наследование и модульность

Все главы курса «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Установка и первый конфиг nginxТюнинг и производительность nginx

Вернуться в «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость