Статьи

Внутри NGINX: как мы создавали для производительности и масштаба

[Эта статья была написана Оуэном Гарреттом]

NGINX является лидером по производительности в Интернете, и все это связано с тем, как разработано программное обеспечение. В то время как многие веб-серверы и серверы приложений используют простую многопоточную или основанную на процессах архитектуру, NGINX отличается сложной архитектурой, управляемой событиями, которая позволяет масштабировать до сотен тысяч одновременных подключений на современном оборудовании.

В  Внутри Nginx  инфографики сверла вниз от архитектуры процесса на высоком уровне , чтобы проиллюстрировать , как NGINX обрабатывает множественные соединения в пределах одного процесса. Этот блог объясняет, как все это работает, более подробно.

Настройка сцены — модель процесса NGINX

Мастер Процесс

Чтобы лучше понять этот дизайн, вам нужно понять, как работает NGINX. NGINX имеет главный процесс (который выполняет привилегированные операции, такие как чтение конфигурации и привязку к портам) и ряд рабочих и вспомогательных процессов.

# service nginx restart
* Restarting nginx
# ps -ef --forest | grep nginx
root     32475     1  0 13:36 ?        00:00:00 nginx: master process /usr/sbin/nginx \
                                                -c /etc/nginx/nginx.conf
nginx    32476 32475  0 13:36 ?        00:00:00  \_ nginx: worker process
nginx    32477 32475  0 13:36 ?        00:00:00  \_ nginx: worker process
nginx    32479 32475  0 13:36 ?        00:00:00  \_ nginx: worker process
nginx    32480 32475  0 13:36 ?        00:00:00  \_ nginx: worker process
nginx    32481 32475  0 13:36 ?        00:00:00  \_ nginx: cache manager process
nginx    32482 32475  0 13:36 ?        00:00:00  \_ nginx: cache loader process

На этом 4-ядерном сервере главный процесс NGINX создает 4 рабочих процесса и несколько вспомогательных процессов, которые управляют кэшем содержимого на диске.

Почему архитектура важна?

Фундаментальной основой любого приложения Unix является поток или процесс. (С точки зрения ОС Linux потоки и процессы в основном идентичны; главное различие заключается в степени, в которой они совместно используют память.) Поток или процесс — это автономный набор инструкций, которые операционная система может запланировать для запуска на ЦП. ядро. Большинство сложных приложений запускают несколько потоков или процессов параллельно по двум причинам:

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

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

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

Как работает NGINX?

NGINX использует предсказуемую модель процесса, настроенную на доступные аппаратные ресурсы:

  • Мастер —  процесс выполняет привилегированные операции , такие как чтение конфигурации и привязку к портам, а затем создает небольшое количество дочерних процессов (следующие три типа).
  • Кэша загрузчик  процесс запускается при запуске , чтобы загрузить кэш диска на основе в память, а затем завершает работу. Это запланировано консервативно, поэтому его потребности в ресурсах низкие.
  • Процесс  диспетчера кэша  периодически запускается и удаляет записи из дисковых кэшей, чтобы сохранить их в заданных размерах.
  • В  рабочие  процессы делают всю работу! Они обрабатывают сетевые соединения, читают и записывают контент на диск и общаются с вышестоящими серверами.

Рекомендуемая в большинстве случаев конфигурация NGINX — запуск одного рабочего процесса на ядро ​​ЦП — обеспечивает наиболее эффективное использование аппаратных ресурсов. Вы настраиваете его, включая  worker_processes auto директиву в конфигурацию:

worker_processes auto;

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

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

Внутри рабочего процесса NGINX

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

Рабочие процессы NGINX начинаются с ожидания событий на слушающих сокетах ( accept_mutex  и  sharding сокетов ядра ). События инициируются новыми входящими соединениями. Эти соединения назначаются   конечному автомату — наиболее часто используется конечный автомат HTTP, но NGINX также реализует конечные автоматы для потокового (необработанного TCP) трафика и для ряда почтовых протоколов (SMTP, IMAP и POP3).

Интернет-запросы

Конечный автомат — это, по сути, набор инструкций, которые сообщают NGINX, как обрабатывать запрос. Большинство веб-серверов, которые выполняют те же функции, что и NGINX, используют аналогичный конечный автомат — разница заключается в реализации.

Планирование конечного автомата

Думайте о государственной машине как о правилах шахмат. Каждая HTTP-транзакция — это игра в шахматы. На одной стороне шахматной доски находится веб-сервер — гроссмейстер, который может принимать решения очень быстро. С другой стороны находится удаленный клиент — веб-браузер, который обращается к сайту или приложению по относительно медленной сети.

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

Блокирующий конечный автомат

Вспомните наше описание процесса или потока как отдельный набор инструкций, которые операционная система может запланировать для запуска на ядре ЦП. Большинство веб-серверов и веб-приложений используют модель «процесс на соединение» или «поток на соединение» для игры в шахматы. Каждый процесс или поток содержит инструкции пройти одну игру до конца. В течение времени, когда процесс выполняется сервером, он проводит большую часть своего времени в «заблокированном» состоянии — ожидая, пока клиент завершит свой следующий ход.

Блокировка ввода / вывода

  1. Процесс веб-сервера прослушивает новые соединения (новые игры, инициированные клиентами) в сокетах прослушивания.
  2. Когда он получает новую игру, он играет в эту игру, блокируя после каждого хода ожидание ответа клиента.
  3. После завершения игры процесс веб-сервера может подождать, чтобы узнать, хочет ли клиент запустить новую игру (это соответствует соединению для поддержки активности). Если соединение закрыто (клиент уходит или происходит тайм-аут), процесс веб-сервера возвращается к прослушиванию новых игр.

Важно помнить, что каждое активное HTTP-соединение (каждая шахматная игра) требует отдельного процесса или потока (гроссмейстер). Эта архитектура проста и легко расширяется с помощью сторонних модулей («новые правила»). Однако существует огромный дисбаланс: довольно легкое HTTP-соединение, представленное дескриптором файла и небольшим объемом памяти, сопоставляется с отдельным потоком или процессом, очень тяжелым объектом операционной системы. Это удобство программирования, но это чрезвычайно расточительно.

NGINX — настоящий гроссмейстер

Возможно, вы слышали об  одновременных выставочных  играх, где один шахматный гроссмейстер играет одновременно с десятками противников?

Кирилл Георгиев

Кирилл Георгиев сыграл 360 человек одновременно в Софии, Болгария.  Его окончательный счет составил 284 победы, 70 ничьих и 6 поражений.

Вот как рабочий процесс NGINX играет в «шахматы». Каждый работник (помните — на каждое ядро ​​процессора обычно приходится один работник) — это гроссмейстер, который может одновременно играть в сотни (на самом деле, сотни тысяч) игр.

Событийная архитектура

  1. Рабочий ожидает событий в сокетах listen и connection.
  2. События происходят в сокетах, и работник обрабатывает их:

    • Событие в слушающем сокете означает, что клиент начал новую игру в шахматы. Рабочий создает новый сокет подключения.
    • Событие в сокете соединения означает, что клиент сделал новый ход. Работник отвечает быстро.

Работник никогда не блокирует сетевой трафик, ожидая ответа своего «оппонента» (клиента). Когда он сделал свой ход, работник немедленно переходит к другим играм, где ходы ожидают обработки, или приветствует новых игроков в дверях.

 Почему это быстрее, чем блокирующая многопроцессная архитектура?

NGINX очень хорошо масштабируется для поддержки сотен тысяч соединений на рабочий процесс. Каждое новое соединение создает другой файловый дескриптор и потребляет небольшой объем дополнительной памяти в рабочем процессе. Существует очень мало дополнительных затрат на соединение. Процессы NGINX могут оставаться привязанными к процессорам. Переключения контекста относительно редки и происходят, когда не нужно выполнять какую-либо работу.

In the blocking, connection-per-process approach, each connection requires a large amount of additional resources and overhead, and context switches (swapping from one process to another) are very frequent.

For a more detailed explanation, check out this article about NGINX architecture, by Andrew Alexeev, VP of Corporate Development and Co-Founder at NGINX, Inc.

With appropriate system tuning, NGINX can scale to handle hundreds of thousands of concurrent HTTP connections per worker process, and can absorb traffic spikes (an influx of new games) without missing a beat.

Updating Configuration and Upgrading NGINX

NGINX’s process architecture, with a small number of worker processes, makes for very efficient updating of the configuration and even the NGINX binary itself.

Обновление конфигурации

Updating NGINX configuration is a very simple, lightweight, and reliable operation. It typically just means running thenginx –s reload command, which checks the configuration on disk and sends the master process a SIGHUP signal.

When the master process receives a SIGHUP, it does two things:

  1. Reloads the configuration and forks a new set of worker processes. These new worker processes immediately begin accepting connections and processing traffic (using the new configuration settings).
  2. Signals the old worker processes to gracefully exit. The worker processes stop accepting new connections. As soon as each current HTTP request completes, the worker process cleanly shuts down the connection (that is, there are no lingering keepalives). Once all connections are closed, the worker processes exit.

This reload process can cause a small spike in CPU and memory usage, but it’s generally imperceptible compared to the resource load from active connections. You can reload the configuration multiple times per second (and many NGINX users do exactly that). Very rarely, issues arise when there are many generations of NGINX worker processes waiting for connections to close, but even those are quickly resolved.

NGINX’s binary upgrade process achieves the holy grail of high-availability – you can upgrade the software on the fly, without any dropped connections, downtime, or interruption in service.

Новый Бинарный

The approach to the graceful reloading of configuration is similar: the NGINX master process forks a new master process that uses the new binary, and then you can signal the old master and workers to gracefully exit.

The entire process is described in more detail in Controlling NGINX.

Conclusion

The Inside NGINX infographic provides a high-level overview of how NGINX functions, but behind this simple explanation is over ten years of innovation and optimization that enable NGINX to deliver the best possible performance on a wide range of hardware while maintaining the security and reliability that modern web applications require.

If you’d like to read more about the optimizations in NGINX, check out these great resources: