Оптимизация рабочих процессов Nginx для максимальной производительности: практическое руководство

Оптимизируйте свой сервер Nginx для работы с высоким трафиком с помощью этого практического руководства по настройке основных директив производительности. Узнайте о лучших практиках установки `worker_processes` в соответствии с ядрами CPU, максимизации параллелизма с помощью `worker_connections` и обеспечения соответствия лимитам файловых дескрипторов ОС (`ulimit`). Статья содержит примеры конфигураций и важные советы по настройке для минимизации задержек и значительного увеличения пропускной способности сервера.

Оптимизация рабочих процессов Nginx для максимальной производительности: практическое руководство

Nginx может обрабатывать множество одновременных соединений при небольшом количестве процессов, но только если его рабочие лимиты соответствуют характеристикам машины. Две настройки, к которым обращаются в первую очередь, — это worker_processes и worker_connections. Они полезны, но их легко переоптимизировать. Установка обоих параметров на огромные значения не создает свободной мощности. Это может просто переместить узкое место на файловые дескрипторы, память, вышестоящие серверы или сетевой стек.

Практическая цель — дать Nginx достаточно рабочих процессов для использования имеющихся ядер CPU, достаточно слотов соединений для реального трафика и достаточно лимитов операционной системы, чтобы избежать достижения потолка при обычных всплесках.

Понимание архитектуры рабочих процессов Nginx

Nginx работает по модели master-worker. Главный процесс отвечает за чтение и проверку конфигурации, привязку к портам и управление рабочими процессами. Он выполняет некритичные задачи, такие как мониторинг системных ресурсов и перезапуск рабочих процессов при необходимости.

Рабочие процессы выполняют основную работу. Эти процессы являются однопоточными (в стандартной компиляции Nginx) и используют неблокирующие системные вызовы. Каждый рабочий процесс эффективно обрабатывает тысячи одновременных соединений с помощью цикла событий, что позволяет одному процессу управлять несколькими запросами без блокировки, что является ключом к производительности Nginx.

Правильная оптимизация включает балансировку количества рабочих процессов (привязку их к ресурсам CPU) и установку максимального количества соединений, которое может обрабатывать каждый рабочий процесс.

Настройка worker_processes: фактор ядер CPU

Директива worker_processes определяет, сколько рабочих процессов должен породить Nginx. Эта настройка напрямую влияет на то, как Nginx использует ресурсы CPU вашего сервера.

Лучшая практика: соответствие рабочих процессов ядрам

Наиболее распространенная и рекомендуемая практика — установить количество рабочих процессов равным количеству ядер CPU на вашем сервере. Это гарантирует эффективное использование каждого ядра без излишних накладных расходов на переключение контекста.

Если количество рабочих процессов превышает количество ядер, операционная система должна часто переключать фокус CPU между конкурирующими процессами Nginx (переключение контекста), что приводит к задержкам и снижению общей производительности.

Использование директивы auto

Для современных версий Nginx (1.3.8 и новее) самым простым и эффективным способом является использование параметра auto. Nginx автоматически определит количество доступных ядер CPU и установит соответствующее количество рабочих процессов.

# Рекомендуемая настройка для большинства развертываний
worker_processes auto;

Ручная настройка

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

# Узнать количество ядер CPU
grep processor /proc/cpuinfo | wc -l

Если в системе 8 ядер, конфигурация будет выглядеть так:

# Ручная установка количества рабочих процессов на 8
worker_processes 8;

Совет: Соответствие количеству доступных ядер — самая безопасная отправная точка. В необычных сценариях с высокой нагрузкой на ввод-вывод вы можете протестировать другое значение, но проверяйте его под реалистичным трафиком, прежде чем оставлять. Для типичной статической отдачи, проксирования и завершения TLS auto обычно является наименее неожиданным выбором.

Настройка worker_connections: фактор параллелизма

Директива worker_connections настраивается в блоке events и определяет максимальное количество одновременных соединений, которое может обрабатывать один рабочий процесс. Это включает соединения с клиентами, соединения с вышестоящими прокси-серверами и внутренние соединения для проверки состояния.

Расчет максимального количества клиентов

Теоретическое максимальное количество одновременных клиентских соединений, которое может обрабатывать ваш сервер Nginx, рассчитывается следующим образом:

$$\text{Max Clients} = \text{worker_processes} \times \text{worker_connections}$$

Если у вас 4 рабочих процесса и 10 000 соединений на процесс, Nginx теоретически может обрабатывать 40 000 одновременных соединений.

Это число является лишь приблизительной верхней границей. Прокси-запрос может одновременно использовать одно клиентское соединение и одно соединение с вышестоящим сервером. Трафик WebSocket и long-polling может удерживать слоты гораздо дольше, чем обычный запрос страницы. Постоянные соединения (keep-alive) также могут оставаться открытыми, выполняя при этом очень мало работы. Если Nginx в основном обслуживает статические файлы, расчет ближе к простой формуле. Если он действует как обратный прокси, оставьте запас.

Установка лимита соединений

На загруженных серверах worker_connections часто устанавливают на несколько тысяч или более, при условии, что лимиты памяти и файловых дескрипторов это позволяют. Не копируйте большое значение вслепую; выбирайте значение, соответствующее ожидаемому параллелизму плюс запас на всплески.

# Пример конфигурации для блока events

events {
    # Максимальное количество одновременных соединений на один рабочий процесс
    worker_connections 16384;

    # Может помочь при всплесках, но проверяйте справедливость под нагрузкой.
    multi_accept on;
}

Ограничение системных лимитов (ulimit)

Критически важно, что настройка worker_connections ограничена лимитом операционной системы на количество открытых файловых дескрипторов (FD) на процесс, который часто контролируется настройкой ulimit -n.

Nginx не может открыть больше соединений, чем ОС позволяет файловых дескрипторов. Поскольку каждое соединение (клиентский сокет, файл журнала, прокси-сокет) требует файлового дескриптора, жизненно важно, чтобы системный лимит был установлен достаточно высоко.

Проверка и увеличение лимита файловых дескрипторов

  1. Проверьте текущий лимит:

    ulimit -n
    
  2. Временно увеличьте лимит (для текущей сессии):

    ulimit -n 65536
    
  3. Постоянно увеличьте лимит (через /etc/security/limits.conf):

    Добавьте следующие строки, заменив nginx_user на пользователя, от которого запущен Nginx (часто www-data или nginx):

    # /etc/security/limits.conf
    nginx_user soft nofile 65536
    nginx_user hard nofile 65536
    

Предупреждение: Убедитесь, что лимит файловых дескрипторов на процесс для пользователя рабочего процесса Nginx выше, чем worker_connections, с дополнительным запасом для журналов, вышестоящих сокетов, файлов кэша и других открытых файлов. Системные лимиты тоже важны, но лимит на процесс чаще всего удивляет людей.

Если Nginx управляется systemd, /etc/security/limits.conf может быть недостаточно. Многие дистрибутивы запускают службы с лимитами из unit-файла. Проверьте активный лимит с помощью:

cat /proc/$(pgrep -o nginx)/limits | grep "open files"

Для переопределения в systemd используйте:

sudo systemctl edit nginx

Затем добавьте:

[Service]
LimitNOFILE=65536

Перезагрузите systemd и перезапустите Nginx в окно обслуживания:

sudo systemctl daemon-reload
sudo systemctl restart nginx

Продвинутая настройка и мониторинг

Помимо основных директив, есть несколько дополнительных соображений, которые могут помочь точнее настроить производительность:

1. Привязка рабочих процессов к ядрам

В средах с высокой производительностью, особенно в системах с несколькими сокетами CPU (архитектуры NUMA), вы можете использовать директиву worker_cpu_affinity. Она указывает ОС ограничить определенные рабочие процессы определенными CPU, что может улучшить производительность за счет сохранения горячих кэшей CPU и избежания проблем с локальностью памяти.

Пример для 8-ядерной системы:

worker_processes 8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;

Эта настройка сложна и обычно полезна только в экстремальных ситуациях с высокой нагрузкой; worker_processes auto достаточно для большинства развертываний.

2. Мониторинг показателей производительности

После применения оптимизаций крайне важно отслеживать их влияние. Используйте модуль Nginx Stub Status (или инструменты, такие как Prometheus/Grafana) для отслеживания ключевых метрик:

Метрика Описание Проверка оптимизации
Active Connections Общее количество обрабатываемых в данный момент соединений. Должно быть ниже теоретического максимума.
Reading/Writing/Waiting Соединения в разных состояниях. Высокое количество Waiting часто указывает на долгоживущие HTTP Keep-Alive (хорошо) или на недостаточные ресурсы обработки (плохо).
Request Rate Запросов в секунду. Используется для измерения фактического улучшения производительности после изменений конфигурации.

Если вы наблюдаете высокую загрузку CPU на всех ядрах и высокую скорость запросов, ваши worker_processes, скорее всего, настроены правильно. Если у вас есть простаивающие ядра CPU во время пикового трафика, рассмотрите возможность проверки конфигурации или проверки блокирующих операций ввода-вывода вне Nginx.

3. Стратегия переполнения соединений

Если сервер достигает максимального лимита соединений (worker_processes * worker_connections), новые соединения могут завершаться ошибкой или стоять в очередях до истечения времени ожидания. Увеличение worker_connections может помочь только в том случае, если Nginx является фактическим узким местом. Если вышестоящие серверы приложений насыщены, повышение лимита может усугубить проблему, поскольку больше запросов будет накапливаться за медленными бэкендами.

Используйте журнал ошибок как сигнал. Сообщения типа worker_connections are not enough прямо указывают на лимиты Nginx. Рост сообщений upstream timed out, connect() failed или ответов 502/504 скорее указывает на пропускную способность бэкенда, сетевые проблемы или настройки тайм-аута.

Разумная начальная конфигурация

Для небольшого или среднего обратного прокси это разумный базовый уровень:

worker_processes auto;
worker_rlimit_nofile 65536;

events {
    worker_connections 8192;
    multi_accept off;
}

Почему multi_accept off? Это консервативное значение по умолчанию во многих системах. Его включение может помочь рабочему процессу быстро обработать очередь ожидающих соединений, но при некоторых шаблонах трафика это может позволить одному рабочему процессу захватить большую партию, пока другие простаивают. Если у вас неравномерный трафик и есть проверенная причина включить его, сделайте это. Если вы настраиваете веб-сервер общего назначения, оставьте базовый уровень простым и сначала измерьте.

Если сервер обрабатывает много соединений WebSocket, Server-Sent Events или долгоживущих потоков API, увеличьте лимит соединений более агрессивно и уделите пристальное внимание памяти. Сервер с 20 000 в основном бездействующих клиентов WebSocket имеет другой профиль, чем сервер, обрабатывающий 20 000 коротких запросов статических файлов.

Как проверить изменение

Перед изменением в production запишите небольшой базовый уровень:

nginx -T | grep -E 'worker_processes|worker_connections|worker_rlimit_nofile'
ss -s
ulimit -n

После изменения проверьте, что Nginx действительно загрузил его:

sudo nginx -t
sudo systemctl reload nginx
ps -o pid,comm,nlwp,pcpu,pmem -C nginx
cat /proc/$(pgrep -n nginx)/limits | grep "open files"

Затем наблюдайте за поведением при реальном трафике. Если все ядра CPU заняты, а задержка растет, Nginx может выполнять полезную работу и достигать предела CPU. Если CPU загружен мало, но соединения ставятся в очередь или истекают по времени, проверьте файловые дескрипторы, насыщение вышестоящих серверов, разрешение DNS, дисковый ввод-вывод или лимиты брандмауэра. Настройка рабочих процессов — это один рычаг, а не вся история производительности.

Чтение цифр в контексте

Распространенная ошибка — считать "активные соединения" тем же самым, что и "активные пользователи". Это не так. Один браузер может открыть несколько соединений для ресурсов. Один клиент API может поддерживать соединение между запросами. Один клиент WebSocket может удерживать соединение часами, отправляя почти нулевой трафик. Когда вы определяете размер worker_connections, думайте в терминах одновременных сокетов, а не людей.

Для обратного прокси также помните о стороне вышестоящего сервера. Если 4 000 клиентов ожидают прокси-ответов, Nginx также может удерживать тысячи вышестоящих сокетов. Вот почему сервер может исчерпать файловые дескрипторы раньше, чем показывает простой расчет со стороны клиента. Это особенно заметно, когда вышестоящее приложение замедляется: запросы остаются открытыми дольше, параллелизм растет, и Nginx начинает потреблять больше сокетов, даже если скорость входящих запросов не изменилась.

Настройки keep-alive также влияют на это. Длинные тайм-ауты keep-alive уменьшают смену соединений, что может помочь загруженным сайтам, но они также дольше удерживают бездействующие сокеты. Очень короткие тайм-ауты keep-alive освобождают сокеты быстрее, но могут увеличить количество рукопожатий TLS и накладные расходы на установку соединения. Идеального значения не существует; используйте форму трафика в качестве руководства. Публичному веб-сайту с множеством коротких посещений может потребоваться другой баланс, чем внутреннему API с небольшим количеством постоянных клиентов.

Если вы настраиваете внутри контейнера, проверьте лимиты внутри контейнера и на уровне хоста или оркестратора. Pod Kubernetes, контейнер Docker или служба systemd могут иметь более низкий лимит nofile, чем оболочка хоста, которую вы использовали для тестирования. Всегда проверяйте запущенный процесс Nginx, а не только вашу сессию входа.

Сводка лучших практик

Директива Рекомендуемое значение Обоснование
worker_processes auto (или количество ядер) Обеспечивает оптимальное использование CPU и минимизирует накладные расходы на переключение контекста.
worker_connections Начните с нескольких тысяч; увеличивайте на основе измеренного параллелизма Обеспечивает запас соединений, не скрывая другие узкие места.
Лимит ОС (ulimit -n) Выше, чем потребности в соединениях на рабочий процесс, с дополнительным запасом Обеспечивает файловые дескрипторы для клиентских сокетов, вышестоящих сокетов, журналов и файлов кэша.
multi_accept Тестируйте перед включением Может помочь при всплесках, но не обязательно лучше для любой рабочей нагрузки.

Лучшая конфигурация рабочих процессов Nginx обычно проста: worker_processes auto, лимит соединений, отражающий реальный параллелизм, и лимиты файловых дескрипторов, достаточно высокие для рабочей нагрузки. Настройте, проверьте активные лимиты процессов и продолжайте следить за журналом ошибок. Если симптомы указывают на вышестоящий сервер, исправьте его, вместо того чтобы заставлять Nginx принимать больше работы, чем приложение может завершить.