Nginx

Материал из Tech_support
Перейти к: навигация, поиск

Глава 14 из книги "Архитектура приложений с открытым исходным кодом", том 2.

Оригинал: The Architecture of Open Source Applications: nginx

Автор: Andrew Alexeev,

Дата публикации: 17 мая 2012 г.

Перевод: А.Кикоть

Дата перевода: 08 апреля 2013 г.

nginx (произносится, как "engine x") - это web-сервер с открытым исходным кодом, написанный российским разработчиком Игорем Сысоевым. С момента опубликования в 2004 году nginx фокусировался на высокой производительности, высокоэффективных параллельных вычислениях и минимизации использования оперативной памяти. Такие дополняющие функции web-сервера возможности, как балансировка нагрузки, кэширование, контроль доступа, контроль пропускной способности и умение эффективно интегрироваться с различными приложениями позволили nginx стать хорошим выбором для web-сайтов с современной архитектурой. В настоящее время nginx занимает второе место среди самых популярных web-серверов с открытым исходным кодом.

14.1. Почему высокоэффективные параллельные вычисления так важны?

В настоящее время Интернет имеет настолько широкое и повсеместное распространение, что сложно себе представить его отсутствие каких-нибудь десять лет назад. Это результат взрывного развития от перехода по ссылкам между текстами с разметкой HTML, основанных на NCSA, а затем и на web-серверах Apache, до 2 миллардов пользователей по всему миру, находящихся всегда на связи. С широким распространением постоянно подключённых к сети персональных компьютеров, мобильных устройств, а теперь и планшетных компьютеров, облик Интернета быстро меняется и целые экономики становятся цифровыми и проводными. Онлайн-сервисы стали более сложными с явным уклоном в сторону мгновенного доступа к постоянно обновляющейся информации и развлечениям. Вопросы обеспечения безопасного ведения бизнеса в Интернете также сильно изменились. Соответственно, web-сайты сейчас гораздо сложнее, чем раньше, и требуют больше инженерных решений для повышения надёжности и масштабируемости.

Одной из самых больших проблем при выборе архитектуры web-сайта всегда был параллелизм. С момента появления web-сервисов необходимость в одновременном выполнении различных операций постоянно растёт. Необходимость одновременного обслуживания популярными web-сайтами сотен тысяч или даже миллионов пользователей - вовсе не редкость. Десятилетие назад главной причиной параллелизма была медленная скорость клиентов, использующих ADSL- или dial-up-соединения. В настоящее время параллелизм обусловлен сочетанием мобильных клиентов и новой архитектуры приложений, которые обычно основываются на поддержании постоянного подключения, что позволяет клиенту получать свежие новости, твиты, новостные ленты друзей и т.п. Ещё одним важным фактором, приведшим к увеличению важности параллелизма, стало изменение поведения современных браузеров, которые открывают от 4 до 6 соединений к web-сайту для ускорения загрузки страницы.

Чтобы проиллюстрировать проблему медленных клиентов представьте себе простой web-сервер на основе Apache, который генерирует относительно короткий ответ размером 100 КБ - web-страницу с текстом или изображением. Генерация страницы и ответ на запрос могут занять доли секунды, но занимают 10 секунд из-за скорости передачи клиенту, имеющему скорость доступа 80 Кбит/с (10 КБ/с). То есть, web-сервер будет сравнительно быстро подготавливать содержимое страницы в 100 КБ и затем оставаться занятым ещё 10 секунд из-за медленной передачи клиенту. А теперь представьте, что у вас есть 1000 подключённых клиентов, которые одновременно запросили получение аналогичных страниц. Если на каждого клиента выделять всего по 1 МБ оперативной памяти, то это привело бы к необходимости выделения 1000 МБ (около 1 ГБ) оперативной памяти для передачи 100 КБ информации. В реальности, типовой web-сервер на базе Apache обычно выделяет более 1 МБ на одно соединение, а эффективная скорость мобильных клиентов, к сожалению, десятки кбит/с. Ситуация с отправкой информации медленным клиентам может быть в какой-то степени улучшена путём увеличения буферов обмена сокетов ядра операционной системы, но это не универсальное решение и может иметь нежелательные побочные эффекты.

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

Следовательно, чтобы справиться с возросшей нагрузкой, связанной с увеличивающейся аудиторией и, как следствие, требованиями к параллелизму, а также справляться с этим и далее, web-сайт должен основываться на ряде очень эффективных "строительных" блоков. Другие части этого уравнения, такие как аппаратная часть (ЦПУ, ОЗУ, НЖМД), пропускная способность сети, системное программное обеспечение и архитектура системы хранения, очень важны, но именно программное обеспечение web-сервера принимает и обрабатывает соединения клиентов. То есть, web-сервер должен быть способен к нелинейному масштабированию с ростом числа одновременных соединений и количества запросов в секунду.

Apache не подходит?

Apache - это доминирующий сейчас в Интернете web-сервер, берущий начало в 1990-х годах. Первоначально его архитектура соответствовала операционным системам и аппаратному обеспечению того времени, соответствовала и состоянию Интернета, где было принято для каждого web-сайта выделять физический сервер с единственным экземпляром Apache. К началу 2000-х годов стало очевидно, что модель с выделенным web-сервером не может удовлетворять растущие потребности web-сервисов. Несмотря на прочную основу для будущего развития, заложенную в Apache, его архитектура предусматривала запуск своей копии для каждого нового соединения, что не подходило для нелинейной масштабируемости web-сайта. В итоге Apache стал web-сервером общего назначения и сосредоточился на увеличении количества разнообразных функций и сторонних расширений для обеспечения универсальной применимости к практически любому виду web-разработки. Однако, за всё необходимо платить: столь богатый и универсальный инструментарий в рамках одной программы снижает её возможности к масштабированию из-за увеличенного использования ЦПУ и памяти на каждое соединение.

Таким образом, аппаратная часть сервера, его операционная система и сетевые ресурсы перестали быть основной проблемой для развития web-сайта, что привело web-разработчиков по всему миру к поиску более эффективных средств организации web-серверов. Около десяти лет назад Daniel Kegel, ведущий разработчик программного обеспечения, объявил, что "настало время, когда web-серверы должны обрабатывать десять тысяч клиентских соединений одновременно" и предложил называть всё это облачными сервисами Интернета. Манифест "C10K" Кегеля (Daniel Kegel) обострил проблему оптимизации web-серверов для одновременной поддержки большого числа клиентских соединений и nginx при таком подходе оказался одним из лучших.

С целью преодоления проблемы C10K в 10'000 одновременных соединений в nginx была заложена другая архитектура, подразумевающая лучшую нелинейную масштабируемость, как по числу поддерживаемых одновременных соединений, так и по числу запросов в секунду. nginx основан на событийно-ориентированной модели (event-based), что позволяет ему не порождать новый процесс или поток для каждого запроса web-страницы, как это делает Apache. В результате возрастание нагрузки стало более равномерным, а использование ресурсов памяти и ЦПУ управляемым. nginx мог теперь обрабатывать десятки тысяч одновременных соединений на сервере с обычной аппаратной частью.

С выходом первой версии nginx стало понятно, что он должен применяться совместно с Apache для выдачи поддерживаемой nginx статической информации вроде HTML, CSS, JavaScript и изображений, что позволяло снизить нагрузку и время отклика серверов приложений на базе Apache. В ходе развития в nginx были добавлены интеграция с приложениями с помощью FastCGI, uswgi или SCGI протоколов и системами распределённого кеширования в оперативной памяти, такими как memcached. Также были добавлены другие полезные функции, такие как обратный прокси-сервер с балансировкой и кешированием. Эти дополнительные возможности превратили nginx в эффективный набор инструментов для построения масштабируемой web-инфраструктуры.

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

Есть ли ещё преимущества при использовании nginx?

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

В последние несколько лет web-архитекторы восприняли идею удаления зависимостей и отделения инфраструктры приложений от web-сервера. Однако, то, что раньше существовало в виде web-сайта, основанного на LAMP (Linux, Apache, MySQL, PHP или Perl), теперь может не только основываться на LEMP ("E" означает "enginx"), но и всё чаще встречается в виде web-сервера с интегрированной инфраструктурой или на том же наборе приложений и баз данных, но взаимодействующих на иных принципах.

nginx очень хорошо подходит для этого, так как обеспечивает весь необходимый функционал: снижение нагрузки при обработке одновременных запросов, снижение времени отклика, поддержка SSL (Secure Socket Layer), работа со статической информацией, сжатие и кеширование, управление отказом обслуживания соединений и запросов и даже HTTP-потоковое вещание мультимедийной информации - всё это делает nginx более эффективным для применения в качестве первого принимающего запорсы web-сервера. Он также реализует непосредственную интеграцию с memcached/Redis или другими NoSQL-решениями для повышения производительности при одновременной обработке большого количества пользователей.

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

Первая строка исходного кода nginx была написана в 2002 году. В 2004 году он был выпущен под лицензией "BSD 2-Clause License". С тех пор количество пользователей nginx постоянно растёт, предлагаются новые идеи, представляются отчёты об ошибках, формируются предложения и замечания - всё это вместе чрезвычайно полезно и выгодно для всего сообщества.

Исходный код nginx является оригинальным и был полностью написан "с нуля" на языке программирования Си. nginx был портирован на множество архитектур и операционных систем, включая Linux, FreeBSD, Solaris, Mac OS, AIX и Microsoft Windows. nginx основывается на своих собственных библиотеках и стандартных модулях, имеющих очень мало внешних зависимостей помимо библиотеки языка C, за исключением zlib, PCRE и OpenSSL, которые могут быть исключены во время сборки при необходимости или по лицензионным соображениям.

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

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, которые позволяют дистанционно контролировать содержимое кеша, и в будущем этот функционал планируется интегрировать в основной дистрибутив.

14.3 Настройки nginx

Вдохновение при создании системы конфигурирования nginx Игорь Сысоев черпал в Apache. Его главная идея состояла в том, что масштабируемая система конфигурации имеет важнейшее значение для web-сервера. Основная проблема масштабируемости проявилась при поддержании больших и сложных конфигураций с большим количеством виртуальных серверов, каталогов, корневых директорий web-сайтов (locations) и наборов данных. В сравнительно больших инсталляциях эта проблема может стать кошмаром, если всё сразу не сделано должным образом, как на уровне приложения, так и на уровне системного инженера.

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

Конфигурационные файлы nginx хранятся в файлах, обычно расположенных в каталоге /usr/local/etc/nginx или /etc/nginx. Основной конфигурационный файл обычно называется nginx.conf. Для достижения лаконичности часть настроек может быть помещена в отдельные файлы, которые будут автоматически объединены с основным на этапе загрузки. Тем не менее, следует отметить, что в настоящее время nginx не поддерживает распределённые конфигурации в стиле Apache (например, файлы .htaccess). Все настройки, определяющие поведение nginx, должны храниться в упорядоченном наборе конфигурационных файлов.

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

Файл настроек nginx может содержать несколько типов блоков директив: основные настройки (main), настройки для http (http), настройки задания виртуальных серверов (server), настройки для указания вышестоящих серверов (upstream), настройки определения местоположения web-сайта (location) и настройки для проксирования электронной почты (mail). Группы настроек никогда не пересекаются. Например, не допускается помещать блок директив location в блок основных настроек main. Кроме того, для исключения двусмысленностей отсутствуют такие настройки, как "глобальный web-сервер". Настройки nginx подразумевают чистоту и логичность, позволяя пользователям поддерживать сложные конфигурационные файлы с тысячами директив. В частной беседе Сысоев сказал: "Locations, directories и другие блоки настроек в общей конфигурации, как это сделано в Apache, мне никогда не нравились и это является причиной, по которой они никогда не появятся в nginx".

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

Хотя некоторые директивы nginx напоминают конфигурацию Apache, настройка nginx - это совсем другой опыт. Например, nginx поддерживает переопределение правил, что в случае с настройкой Apache потребовало бы ручной правки конфигурационных файлов. Реализации способов перезаписи также отличаются.

В целом, настройки nginx поддерживают несколько оригинальных механизмов, которые могут быть очень полезны при конфигурировании web-сервера с минимальными требованиями. Имеет смысл упомянуть именованные переменные (variables) и директиву try_files, которые в некоторой степени уникальны. Именованные переменные были реализованы для обеспечения ещё более мощного механизма управления web-сервером на этапе загрузки конфигурации. Именованные переменные уже пре-компилированы и оптимизированы для быстрого разрешения в значения. Разрешение переменной в значение выполняется по требовнию, то есть значение, как правило, расчитывается один раз и остаётся неизменным в кеше всё время жизни конкретного запроса. Именованные переменные могут использоваться с различными конфигурационными директивами, что обеспечивает дополнительную гибкость при описании реакции на запросы по условию.

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

14.4. Внутреннее устройство nginx

Как уже упоминалось ранее, nginx состоит из ядра и некоторого количества модулей. Ядро nginx отвечает за базовый функционал web-сервера и функционал обратного проксирования web и электронной почты, что позволяет предоставлять доступ к реализованным в ядре сетевым протоколам, создавать необходимые среды исполнения и обеспечивать "бесшовное" взаимодействие между модулями. Тем не менее, большинство функций, специфичных для протоклов и приложений, реализуется модулями, а не ядром.

Внутри себя nginx обрабатывает соединения с помощью каналов (pipeline), цепочек команд (chain) или модулей. Другими словами, для каждой операции находится модуль, который и выполняет соответствующую работу (например, сжатие, преобразование данных, выполнение серверных сценариев, взаимодействие с вышестоящими серверами приложений с применением FastCGI или uwsgi протоколов, взаимодействие с memcached).

Есть два модуля, размещённые где-то между ядром и модулями с реальным функционалом. Это модули http и mail. Эти модули обеспечивают дополнительный уровень абстракции между ядром и низкоуровневыми компонентами. В них реализована обработка последовательностей событий, связанная с протоколами прикладного уровня HTTP, SMTP или IMAP. Вместе с ядром эти модули обеспечивают поддержание правильного порядка вызовов соответствующих функциональных модулей. В то время как протокол HTTP реализован в виде части модуля http, уже есть планы по реализации его в виде отдельных функциональных модулей в связи с необходимостью поддержки других протоколов, например, SPDY (смотрите "SPDY: An experimental protocol for a faster web").

Функциональные модули можно разделить на следующие типы: модули обработки событий, модули обработки фазы, выходные фильтры, модули обработки именованных переменных, модули реализации протоколов, модули взаимодействия с вышестоящими серверами и балансировщики нагрузки. Большинство из этих модулей дополняют HTTP-функциональность nginx, хотя модули обработки событий и реализации протоколов также используются и в модуле mail. Модули обработки событий реализуют зависимые от операционной системы механизмы извещения о событиях, например, kqueue или epoll. Модуль обработки событий, используемый в nginx, зависит от операционной системы, на которой он запущен, и настроек. Модули поддержки протоколов позволяют nginx взаимодействовать с использованием HTTP, TLS/SSL, SMTP, POP3 и IMAP.

Типовой цикл обработки HTTP-запроса выглядит следующим образом:

Клиент посылает HTTP-запрос Ядро nginx выбирает соответствующий обработчик фазы на основе сопоставления содержимого запроса и настроенных корневых каталогов web-сайтов (location) Если location настроен в качестве балансировщика нагрузки, то nginx выбирает вышестоящщий сервер для проксирования Обработчик фазы выполняет свою работу и каждый выходной буфер от него подаётся на вход первого фильтра Обработчик фазы первого фильтра подаёт данные на второй фильтр Обработчик фазы второго фильтра подёт данные на третий и т.д. Подготовленный ответ отсылается клиенту Порядок вызова модулей в nginx чрезвычайно настраиваемый. Он осуществляется с помощью ряда функций обратного вызова с указателями на исполняемые функции. Однако, недостатоком такого подхода является высокий входной порог для программистов, которые хотели бы писать свои собственные модули, так как им потребуется разобраться в этом и точно указывать как и когда модуль должен работать. Для облегчения этой нагрузки постоянно улучшаются API и документация для разработчиков.

Несколько примеров мест, где могут быть подключены модули:

перед считыванием и применением конфигурационного файла для каждой директивы location и server везде, где она встретится момент применения основной конфигурации момент инициализации сервера (например, хоста или порта) момент объединения конфигурационных файлов с основной конфигурацией момент инициализации секции location или её объединения с конфигурацией "родительского" сервера момент запуска и останова управляющего процесса момент запуска или завершения исполнителя момент обработки запроса момент фильтрации заголовка или содержимого ответа момент выбора вышестоящего сервера, инициирования запроса к нему и повторного запроса момент обработки ответа от вышестоящего сервера момент завершения взаимодействия с вышестоящим сервером Последовательность действий цикла генерации ответа внутри исполнителя выглядит следующим образом:

Запуск цикла ngx_worker_process_cycle() Обработка событий с использованием зависимых от операционной системы механизмов (таких, как epoll или kqueue) Приём событий и управление выполнением соответствующих им действий Обработка или проксирование заголовка или содержимого запроса Генерация содержимого ответа (заголовок и данные) и передача его клиенту Завершение обработки запроса Сброс таймеров и событий Шаги 5 и 6 выполнения цикла обработки запроса обеспечивают поэтапную генерацию ответа и передачу клиенту.

Более детальное описание процесса обработки HTTP-запроса может выглядеть следующим образом:

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

Обработчики фазы обычно выполняют четыре задачи: считывают расположение корневого каталога web-сайта (location), генерируют соответствующие ответы на запросы, выполняют отправку заголовков и данных ответа. Обработчик имеет всего один аргумент: структура с описанием запроса. В структуре описания запроса определяется много полезной информации о запросе клиента: метод запроса, URI и заголовок.

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

преобразование URI на уровне сервера поиск конфигурации в которой будет обрабатываться запрос преобразование URI на уровне location (что может привести к запросу на возврат к предыдущей фазе) проверка доступа обработка директив try_files журналирование (запись лога) Для создания ответа на запрос nginx попытается передать его подходящему обработчику генерирования ответа. В зависимости от настроек location nginx может попробовать безусловные обработчики perl, proxy_pass, flv, mp4 и т.д. Если запрос не соответствует ни одному из вышеперечисленных обработчиков, то будут последовательно перебраны следующие модули в указанном порядке: random index, index, autoindex, gzip_static, static.

Более подробное описание модулей index приведено в документации nginx. Эти модули отвечают за обработку запросов на адреса, завершающиеся слешем. Если специализированные модули не подходят (например, mp4 или autoindex), то запрашиваемые данные считаются файлом или каталогом (то есть, статическими данными) и обрабатываются обработчиком static. Для каталога URI будет автоматически преобразован в адрес, завершающийся слешем, и затем выполнится перенаправление HTTP-запроса.

Данные, сгенерированные обработчиком, передаются на фильтры. Задание ассоциаций для фильтров также выполняется на уровне location с возможностью настройки сразу нескольких фильтров. Фильтры выполняют задачу дополнительной обработки сгенерированного обработчиками потока. Порядок запуска фильтра определяется на этапе компиляции. Все фильтры, как поставляемые вместе с nginx, так и сторонние, могут быть настроены на этапе сборки. В текущей реализации nginx фильтры могут быть применены только к выходным данным. Механизма применения фильтров к входным данным в настоящее время не существует. Применение фильтров к входному потоку появится в будущих версиях nginx.

Фильтры строятся согласно определённому шаблону проектирования. Фильтр вызывается, начинает работу, вызывает следующий фильтр и так до тех пор, пока не будет вызван последний фильтр в этой цепочке. После этого nginx завершает подготовку ответа. Следующему фильтру не обязательно ждать, пока предыдущий завершит работу. Следующий фильтр может начинать работу сразу, как только появится порция обработанных данных предыдущим фильтром (по аналогии с каналами (pipeline) Unix). В свою очередь, генерируемый на выходе последнего фильтра ответ может быть передан клиенту до получения полного ответа от вышестоящего сервера.

Фильтры разделяются на фильтры заголовков и данных. nginx при подготовке ответа на запрос подаёт заголовки и данные на соответствующие фильтры раздельно.

Фильтрация заголовка состоит из трёх основных шагов:

Принятие решения о реакции на запрос Обработка ответа Вызов следующего фильтра Фильтры данных преобразуют содержимое ответа. Могут быть приведены следующие примеры фильтров:

серверные включения фильтрация XSLT фильтрация изображений (например, изменение размера "на лету") конвертирование кодировки сжатие gzip передача данных с использованием механизма Chunked transfer encoding После завершения работы цепочки фильтров ответ передаётся на отправку. Одновременно с отправкой могут работать два специальных фильтра copy и postpone. Фильтр copy отвечает за заполнение буферов в оперативной памяти соответствующим ответом, который может храниться во временном каталоге прокси. Фильтр postpone используется для выполнения под-запросов.

Подзапросы являются очень важным механизмом обработки по типу "запрос-ответ". Подзапросы также являются одним из самых мощных механизмов nginx. В результате применения подзапросов nginx может вернуть в качестве результата другой URL, а не тот, что запрашивал клиент. В некоторых библиотеках разработки (frameworks) это называется внутренним перенаправлением. Однако, nginx идёт ещё дальше - помимо выполнения нескольких подзапросов с несколькими фильтрами и объединением всего этого в один ответ, подзапросы могут быть вложенными и выстроенными в иерархическую структуру. Подзапросы могут выполнять свои под-подзапросы, которые в свою очередь могут инициировать под-под-подзапросы. Подзапросы можно связать с файлами на диске, другими обработчиками или вышестоящими серверами. Подзапросы - это очень полезный механизм, позволяющий дополнять ответ, выполняя под-запросы на основе данных подготовленного ранее ответа. Например, модуль SSI (серверные включения) использует фильтр для анализа содержимого возвращаемого документа, а затем подменяет директивы include к конкретным URL. Или может быть приведён ещё такой пример: использование фильтра для извлечения документа по указанной ссылке URL, его обработка, сохранение и возвращение ссылки URL на уже обработанный документ.

Перенаправление запросов (upstream) и проксирование также заслуживают упоминания. Перенаправление можно описать как совокупность обработчика данных запроса и обратного прокси (proxy_pass). Upstream-модули предназначены в основном для отправки запросов на вышестоящий сервер (или встроенную подсистему обработки - backend) и получения ответов от него. Здесь вызов фильтров не предусмотрен. Что точно делает upstream-модуль, так это запускает функции обратного вызова, когда вышестоящий сервер готов для записи или чтения. В nginx реализованы следующие функции обратного вызова:

создание буфера запросов (или цепочек из них), который будет направлен в вышестоящий сервер повторная инициализация или сброс соединения с вышестоящим сервером (что происходит непосредственно перед повторным запросом) обработка первых битов ответа вышестоящего сервера и сохранение указателей на полученные от него данные прерывание запросов (что происходит при преждевременном завершении работы клиента) завершение обработки запроса при получении всех данных от вышестоящего сервера удаление части ответа (например, завершающей части пакета - trailer) Модули балансировки нагрузки дополняют обработчик proxy_pass и предоставляют возможность выбора вышестоящего сервера в случае доступности нескольких. Балансировщик нагрузки включается соответствующей дерективой в конфигурационном файле, обеспечивает дополнительный функционал при инициализации вышестоящего сервера (разрешение вышестоящего сервера по DNS-имени и т.д.), инициализирует структуры для описания соединения, принимает решение о выборе вышестоящего сервера для перенаправления запроса и обновляет статистику. В настоящее время nginx поддерживает два способа балансировки нагрузки: циклический (round-robin) и IP-хеш (IP-hash).

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

Также есть несколько других интересных модулей, обеспечивающих применение дополнительного набора именованных переменных (variables) в конфигурационном файле. В то время, как в различных модулях создаются и обновляются наборы именованных переменных, есть два модуля, полностью посвящённых именованным переменным geo и map. Модуль geo используется для облегчения отслеживания клиентов по их IP-адресам. Этот модуль может создавать различные именованные переменные в зависимости от IP-адреса клиента. Модуль map позволяет создавать именованные переменные на основе других именованных переменных, предоставляя, по-сути, гибкий механизм отображения имён хостов и динамических переменных. Этот тип модулей может быть назван - "обработчик именованных переменных".

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

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

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

Задача управления оперативной памятью в nginx решается распределителем пула (pool allocator). Области разделяемой памяти используются для организации мьютексов, кеша метаданных, кеша SSL-сессий и хранения информации об управлении пропускной способностью (лимиты). В nginx для управления разделяемой памятью используется slab-распределитель. Для потокобезопасного использования разделяемой памяти используются механизмы блокировки доступа (мьютексы и семафоры). Также при организации сложных структур данных в nginx используются красно-чёрные деревья (red-black tree). Красно-чёрные деревья используются для хранения кеша метаданных в разделяемой памяти, отслеживания нерегулярных значений location и некоторых других задач.

К сожалению, всё вышеперечисленное не было документировано в последовательной и простой манере, что делает разработку сторонних модулей для nginx довольно сложной. Тем не менее, некоторые хорошие материалы существуют. Например, материалы, изданные Эваном Миллером (Evan Miller). Такие материалы требуют огромной работы по обратному инжинирингу и изучению работы модулей, что для многих сравнимо с чёрной магией.

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

14.5. Выводы

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

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

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

Последнее, но не менее важное, что стоит отметить, это то, что не смотря на небольшое сообщество разработчиков nginx, сторонние модули и расширения всегда были очень важной составляющей его популярности. Работа, выполненная Evan Miller, Piotr Sikora, Valery Kholodkov, Zhang Yichun (agentzh) и другими талантливыми разработчиками высоко ценится сообществом nginx и его непосредственными разработчиками.

nginx -> ( .htaccess )

Дополнительные материалы