Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

NGINX

Глава 14 из книги "Архитектура приложений с открытым исходным кодом", том 2.
Оригинал: The Architecture of Open Source Applications: nginx
Автор: Andrew Alexeev,
Перевод: А.Кикоть

14.2. Обзор архитектуры Nginx

Традиционные процессно- или потоко-ориентированные модели подразумевают при одновременном обслуживании соединений порождение нового процесса или потока на каждое соединение и его блокирование при сетевых операциях или операциях ввода/вывода. В зависимости от способа применения такие модели могут быть крайне неэффективными с точки зрения использования ЦПУ и оперативной памяти. Порождение нового процесса или потока влечёт за собой подготовку новой среды окружения с выделением в оперативной памяти кучи (heap), стека и нового контекста исполнения. ЦПУ тратит дополнительное время на создание этих объектов, что, в конечном итоге, может привести к снижению производительности из-за частого переключения контекста исполнения. Все перечисленные выше проблемы проявляются в web-серверах со старой архитектурой, таких как Apache. Это компромисс между богатым функционалом и оптимальным использованием ресурсов сервера.

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

nginx для мультиплексирования и извещения о событиях использует один главный процесс, а исполнение конкретных задач назначает отдельным подчинённым процессам. Обработка соединений выполняется в высокоэффективных циклах обработки событий, помещённых в потоки. Такие потоки называются "исполнители" (workers), а их количество ограничено. В рамках каждого "исполнителя" nginx может обрабатывать тысячи одновременных подключений и запросов в секунду.

Структура исходного кода

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

Модульная архитектура nginx позволяет разработчикам расширять набор функций без изменения его ядра. Модули nginx подразделяются на несколько типов: модули ядра (core modules), обработчики событий (event modules), обработчики фазы (phase handlers), модули реализации протоколов (protocols), обработчики именованных переменных (variable), фильтры, модули перенаправления запросов на вышестоящие серверы (upstreams) и балансировщики нагрузки (load balancers). В настоящее время nginx не поддерживает динамически загружаемые модули. То есть, модули компилируются вместе с ядром в один исполнимый файл на этапе сборки. Однако, поддержка загружаемых модулей и ABI запланирована на следующие major-релизы. Более подробную информацию о роли различных модулей можно найти в разделе 14.4.

При обработке операций, связанных с приёмом, обработкой и управлением сетевыми подключениями, извлечением данных, а также при дисковых операциях ввода/вывода, nginx для повышения производительности использует специфические механизмы уведомления о событиях, такие как kqueue, epoll и event ports, доступные в операционных системах Linux, Solaris и семействе BSD. Цель состоит в максимальном использовании возможностей операционной системы для обеспечения своевременной обратной связи, по своей природе асинхронной, при обработке входящего/исходящего трафика, дисковых операций, чтения или записи в сокеты, организации тайм-аутов и т.п. Используемые в nginx различные методы мультиплексирования и ускоренного ввода/вывода в значительной степени оптимизированы для каждой Unix-подобной операционной системы, на которой он запускается.

Обзорная схема архитектуры nginx представлена на рисунке 14.1

Рисунок 14.1: Схема архитектуры nginx

Модель исполнителя

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

Непрерывный цикл обработки является самой сложной частью исходного кода исполнителя. Здесь широко используются внутренние вызовы и идея асинхронной обработки. Асинхронные операции реализованы с применением модульности, механизма сообщений о событиях, широкого использования функций обратного вызова (callback functions) и великолепно доработанных таймеров. В целом, во всём прослеживается принцип: все операции должны быть неблокирующими настолько, насколько это возможно. Единственная ситуация, когда nginx всё-таки применяет блокировку - это нехватка производительности дисковой подсистемы для исполнителя.

Так как nginx не порождает новый процесс или поток для каждого соединения, использование оперативной памяти очень экономичное и в подавляющем большинстве случаев крайне эффективное. nginx экономно использует ЦПУ в виду отсутствия постоянных созданий и удалений контекстов процесса или потока. Всё что делает nginx, это проверяет состояние сетевого подключения и дисковой подсистемы, инициирует создание новых соединений, переносит их обработку в цикл исполнителя и асинхронно обрабатывает их до завершения, после чего соединение освобождается и удаляется из цикла обработки исполнителя. Сочетание бережного использования вызовов syscall с аккуратной реализацией интерфейсов распределителей памяти pool и slab (pool and slab memory allocators) позволяет nginx достигать от умеренной до низкой загрузки ЦПУ даже при экстремальных нагрузках.

В связи с тем, что nginx использует несколько исполнителей для обработки соединений, он хорошо масштабируется на несколько ядер ЦПУ. В целом, распределение исполнителей по ядрам позволяет полностью использовать многоядерные архитектуры и предотвращает простой потоков и блокировки. В итоге, отсутствует нехватка ресурсов, а механизмы их контроля изолированы каждый в своём потоке исполнителя. Такая модель также позволяет достичь лучшей масштабируемости дисковой подситемы, способствует более эффективному её использованию и позволяет избежать блокировок при операциях ввода/вывода. В результате, ресурсы сервера используются более эффективно с распределением нагрузки между исполнителями.

Для достижения заданных паттернов использования дисковой подсистемы и ЦПУ число исполнителей может быть скорректировано. Для этого есть несколько основных правил, которые системные администраторы должны попробовать для своих рабочих нагрузок. Общие рекомендации могут быть следующими: если предполагается высокая нагрузка на ЦПУ (например, обработка большого количества TCP/IP запросов, обработка SSL или сжатие), то число исполнителей должно совпадать с количеством процессорных ядер; если предполагается высокая нагрузка на дисковую систему ввода/вывода (например, обслуживание запросов на выдачу данных или высоконагруженное проксирование), то число исполнителей должно быть в полтора-два раза больше, чем количество процессорных ядер. Некоторые инженеры выбирают количество исполнителей по количеству систем хранения или дисков, но эффективность такого подхода зависит от типа и конфигурации дисковой подсистемы.

Одной из основных проблем, которую разработчики nginx будут решать в ближайщих версиях, является существенное уменьшение количества блокировок при дисковых операциях ввода/вывода. На данный момент, если одному из исполнителей не хватает производительности дисковой подсистемы, то он может не снять блокировку на дисковый ввод/вывод пока не завершит свою операцию. Однако, существует целый ряд механизмов и директив конфигурационного файла для смягчения сценариев с блокировкой дискового ввода/вывода. В частности, комбинация опций sendfile и AIO обычно обеспечивает достаточный запас по производительности дисковой подсистемы. То есть, развёртывание nginx должно планироваться с учётом типа информации, к которой будет обеспечиваться web-доступ, объёма доступной оперативной памяти и архитектуры системы хранения данных.

Другая проблема в существующей модели исполнителя - это ограниченная поддержка встраиваемых скриптовых сценариев. В стандартной поставке nginx поддерживается встраивание скриптов только на языке Perl. Этому есть простое объяснение: встроенный скрипт может заблокировать выполнение любой операции или неожиданно завершиться. Любой из этих двух вариантов поведения может привести к зависанию исполнителя, что отразится на тысячах одновременных соединений, которые он обслуживал. Планируется проделать большую работу, чтобы сделать встраивание скриптовых языков более простым, надёжным и пригодным для широкого спектра применений.

Назначение процессов nginx

nginx запускает несколько процессов в оперативной памяти: один главный процесс и несколько процессов исполнителей. Также есть несколько специализированных процессов, в частности, загрузчик кеша и диспетчер кеша. В nginx версии 1.1 все процессы однопоточные. Все процессы для взаимодействия между собой используют механизмы разделяемой памяти (shared-memory machanisms). Управляющий процесс nginx (master process) запускается с привелегиями пользователя root. Загрузчик кеша, диспетчер кеша и исполнители запускаются от имени обычного непривелегированного пользователя.

Управляющий процесс отвечает за выполнение следующих задач:
  • считывание и проверка на корректность файла настроек
  • создание, подключение и удаление сокетов
  • запуск, завершение и поддержание заданного количества исполнителей
  • загрузка новых настроек без остановки работы
  • управление бинарными обновлениями "на лету" (начиная от замены исполнимого файла и заканчивая откатом к предыдущему состоянию при необходимости)
  • повторные открытия лог-файлов
  • трансляция встроенных скриптов на языке Perl

Процессы исполнителей принимают, управляют и обрабатывают соединения от клиентов, обеспечивают обратное проксирование и фильтрацию, то есть, делают почти всё, на что спобен nginx. Что касается контроля за работой nginx, то системный администратор должен следить за состоянием процессов исполнителей, так как, фактически, именно они изо дня в день выполняют все операции web-сервера.

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

Диспетчер кеша в основном отвечает за отслеживание истечения срока действия записей и маркирование их как недействительных. Он остаётся в оперативной памяти всё время работы nginx и в случае сбоя перезапускается вместе с главным процессом.

Краткий обзор кеширования в nginx

Кеширование в nginx реализовано в виде иерархически организованного хранилища в файловой системе. Ключи кеша настраиваемые, а информация, попадающая в кеш, может контролироваться с помощью различных специфичных для каждого типа запросов параметров. Кеш ключей и кеш мета-данных хранятся в сегменте разделяемой памяти, к которой могут получить доступ диспетчер кеша и исполнители. В настоящее время кеширование в оперативную память отсутствует, кроме возможной оптимизации с помощью механизма виртуальной файловой ситемы средствами операционных систем. Кеш для каждого запроса располагается в отдельном файле в файловой системе. Иерархией кеша (уровни и способы именования) можно управлять с помощью директив конфигурационного файла nginx. При записи запроса в структуру каталогов кеша путь и имя файла вычисляются с помощью MD5-хеша проксируемого адреса URL.

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


Это произведение распространяется в соответствии с лицензией Creative Commons Attribution 3.0 Unported license. Для получения более детальной информации, пожалуйста, ознакомьтесь с полным описанием лицензии.


Далее: 14.3 Настройки nginx