в чем разница между nginx и apache

Apache против Nginx: плюсы и минусы для WordPress

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

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

По оценкам, из всего Интернета в совокупности Apache Server и Nginx вместе составляют 50% всего веб-трафика.

Отличия

Основное различие заключается в том, как обрабатываются соединения.

Проще говоря, Apache использует разветвленное многопоточное решение или keep-alive, которое поддерживает соединение для каждого пользователя.

С другой стороны, Nginx использует неблокирующий цикл событий, который объединяет соединения, работающие асинхронно через рабочие процессы.

в чем разница между nginx и apache. Смотреть фото в чем разница между nginx и apache. Смотреть картинку в чем разница между nginx и apache. Картинка про в чем разница между nginx и apache. Фото в чем разница между nginx и apacheв чем разница между nginx и apache. Смотреть фото в чем разница между nginx и apache. Смотреть картинку в чем разница между nginx и apache. Картинка про в чем разница между nginx и apache. Фото в чем разница между nginx и apache в чем разница между nginx и apache. Смотреть фото в чем разница между nginx и apache. Смотреть картинку в чем разница между nginx и apache. Картинка про в чем разница между nginx и apache. Фото в чем разница между nginx и apache

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

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

Во-первых, давайте посмотрим на два проекта и сделаем четкий обзор.

Apache

Nginx

Будучи преемником Apache, Nginx имеет преимущество в понимании ошибок и проблем производительности параллелизма, которые могут произойти благодаря его очень быстрой асинхронной схеме цикла событий.

Для статического контента это работает очень быстро. Что касается динамического контента, например PHP, Nginx не имеет возможности обрабатывать это с помощью модуля, как это делает Apache. Но это не помеха, поскольку для достижения этого используется FastCGI. Это очень хорошо работает в сочетании с пулами соединений php fpm и memcache.

Требования WordPress

Оба Apache и Nginx поддерживают php fpm. Это менеджер FastCGI, forked process manager для PHP, который может использоваться для обеспечения очень быстрого времени отклика. Выполняясь как демон на сервере, он будет инициализировать процессы, как только они потребуются.

Настройка PHP FPM с помощью Apache

Пользователи Ubuntu и Debian могут устанавливать необходимые пакеты с помощью aptitude через:

Теперь включите модуль в apache:

Затем в файле конфигурации /etc/apache2/conf-available/php7.0-fpm.conf добавьте следующее:

Кроме того, в вашем VirtualHost для WordPress (путь по умолчанию /etc/apache2/sites-available/000-default.con f) добавьте следующее:

Теперь перезапустите apache, и готово

Создайте файл с содержимым вида и откройте его в своем браузере. Теперь PHP будет работать с FPM.

Теперь проверьте свой блог WordPress. Обратили внимание на разницу?

Настройка PHP FPM с Nginx

Пользователи Ubuntu и Debian могут установить пакет следующим образом:

Теперь в вашем файле конфигурации (по умолчанию /etc/nginx/sites-available/default) в блоке сервера вам необходимо добавить конфигурацию FastCGI следующим образом:

Здесь мы используем фрагмент из Nginx, чтобы установить параметры cgi и передать fastcgi соединение сокета.

Затем убедитесь, что вы установили cgi.fix_pathinfo = 0 в php ini, поскольку настройка по умолчанию нарушает конфигурацию. Измените /etc/php/7.0/fpm/php.ini и установите:

Теперь вы можете сохранить файл и перезагрузить PHP FPM. Сделайте это через:

Наконец, мы можем проверить в своем браузере, чтобы подтвердить, что сервер теперь использует PHP FPM с Nginx.

Выполнение mod_rewrite в Nginx

Чтобы ваш блог WordPress работал с Nginx, просто добавьте следующее к части try_files вашей конфигурации Nginx:

Если вы используете каталог для своего блога WordPress, задайте следующее:

Перезапустите Nginx, и у вас будет работать перезапись URL.

Оптимальные настройки

У вас есть много вариантов оптимизации WordPress посредством кеширования на сервере с помощью memcache, varnish, а также на уровне приложения WordPress с помощью плагинов, которые легко позволят вам получить доступ к этому.

Кэш статического содержимого

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

Блок местоположения для этой конфигурации статических субдоменов будет выглядеть так:

Если вы хотите настроить кеширование по всему проекту, просто добавьте следующие четыре строки в свои конфигурации nginx.conf:

Важно: open_file_cache_errors будет кэшировать фактические ошибки 404, поэтому лучше отключить это, если вы используете балансировщик нагрузки.

Пулы подключения PHP-FPM

Вы можете настроить несколько конфигураций, например:

В каждом из следующих вариантов мы можем установить множество конфигураций:

С помощью этого вы можете указать параметры конфигурации PHP-FPM, такие как pm.max_children, и вы также можете указать переменные окружения и установить здесь параметры имени пользователя и группы.

Балансировщик нагрузки Nginx

Если вы собираетесь получать много трафика, то вам, вероятно, захочется настроить балансировщик нагрузки для использования с настройкой php-fpm.

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

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

Примерная конфигурация будет выглядеть так. Сначала мы начинаем с модуля upstream:

Здесь каждый backend1.example.com имеет собственную конфигурацию Nginx, зеркало того, как сайт был до того, как он имел балансировщик нагрузки. Nginx будет выбирать, какой сервер использовать для каждого запроса.

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

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

Затем нам нужно передать это серверу через прокси-сервер, используя backend upstream, который мы только что определили ранее:

Наконец, по этой теме, также для вашей справки приведено руководство nginx по обслуживанию статического содержимого и наилучшим параметрам конфигурации. Обратите внимание на использование tcp_nopush и sendfile для Mp3, например.

Миграция Apache в Nginx

Помимо чтения руководства Nginx и самостоятельного внесения изменений, вы можете использовать инструмент apache2nginx с открытым исходным кодом для перевода вашей конфигурации с Apache на Nginx.

Следуйте инструкциям по установке apache2nginx README, и после установки вы сможете перенести файлы конфигурации, просто выполнив:

Теперь вы можете проверить конфигурацию и попробовать ее в установке Nginx. Вы можете обнаружить, что перевод не был совершенным, но он даст вам основание для начала.

Вывод

Для скорости и производительности Nginx является очевидным выбором вместо Apache, но это не означает, что Apache не может обрабатывать некоторый трафик. Если вы планируете перейти на первую страницу Reddit в ближайшее время, вероятно, вам стоит взглянуть на получение более эффективного решения с Nginx и PHP-FPM.

Миграция вашего WordPress в Nginx не очень сложна, и конфигурация для Nginx, очень проста и удобна в доступе по сравнению с Apache.

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

Существует множество способов настройки обоих серверов, поэтому хорошее решение можно найти почти всегда, независимо от требований. На данный момент кажется, что Apache всегда будет выбором по умолчанию для широко доступного хостинга cPanel, благодаря инструменту настройки EasyApache, который поставляется вместе с ним.

В будущем, возможно, больше хостов примет инструменты Nginx cPanel, такие как Engintron, которые также предоставляют Nginx для cPanel.

На данный момент, если вы хотите переключиться на WordPress с поддержкой Nginx, вам нужно будет настроить Linux VPN на DigitalOcean, AWS или на другом хостинг-провайдере.

Источник

Разница между nginx и apache с примерами

Во время собеседований на роль linux/unix администратора во многих IT-компаниях спрашивают, что такое load average, чем nginx отличается от apache httpd и что такое fork. В этой статье я постараюсь объяснить, что рассчитывают услышать в ответ на эти вопросы, и почему.

Здесь важно очень хорошо понимать основы администрирования. В идеальной ситуации при постановке задачи системному администратору выставляют ряд требований. Если же ситуация не идеальная, то, по сути, требование к администратору одно: «Хочу, чтобы всё работало». Иными словами, сервис должен быть доступен 24/7 и, если какое-то решение не удовлетворяет этим требованиям (масштабирование и отказоустойчивость относятся к доступности), то можно сказать, что администратор плохо сделал свою работу. Но если разные решения двух администраторов работают 24/7, как понять, какое из них лучше?

Хороший системный администратор при выборе решения при заданных требованиях ориентируется на два условия: минимальное потребление ресурсов и их сбалансированное распределение.

Вариант, когда одному специалисту нужно 10 серверов для выполнения задания, а второму всего 2, мы рассматривать не будем, что тут лучше – очевидно. Далее под ресурсами я буду понимать ЦПУ (cpu), ОЗУ (ram) и диск (hdd).

Давайте рассмотрим ситуацию: один администратор создал решение, которое требует 10% cpu, 5% ram и 10% hdd от всего вашего оборудования, а второй использовал для этого 1% cpu, 40% ram и 20% hdd. Какое из этих решений лучше? Тут все становится уже не так очевидно. Поэтому хороший администратор всегда должен уметь грамотно подобрать решение, исходя из имеющихся ресурсов.

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

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

1) socket — выделяет место в буфере ядра ОС под наш сокет. Адрес выделенного места возвращается из функции в программу;
2) bind — позволяет менять информацию в структуре сокета, которую нам выделила ОС linux по команде socket;
3) listen – так же как и bind меняет данные в нашей структуре, позволяя указывать ОС, что мы хотим принимать подключения по этому сокету;
4) connect – говорит нашей ОС, что она должна подключиться к другому удаленному сокету;
5) accept – говорит нашей ОС, что мы хотим принять новое подключение от другого сокета;
6) read – мы просим ОС выдать нам из своего буфера определенное количество байт, которое она получила от удаленного сокета;
7) write – мы просим ОС послать определенное количество байт на удаленный сокет.

Для того, чтобы установить соединение, нам нужно создать сокет в памяти linux, прописать в нем нужные данные и выполнить подключение к удаленной стороне.

socket → bind → connect → read/write

Но если вы доверяете ОС сделать выбор исходящего порта за вас (а так же и ip адреса), то bind делать необязательно:

socket → connect → read/write

Для того, чтобы принимать входящие сообщения, нам нужно выполнить:

socket → bind → listen → accept → read/write

Теперь мы знаем достаточно для того, чтобы написать программу. Приступаем непосредственно к написанию, используя си. Почему си? Потому что в этом языке команды называются так же, как системные вызовы (за редким исключением, типа fork).

Компилируем и запускаем наш демон:

Смотрим, что получилось:

Процесс находится в состоянии sleep (S+ в команде ps).

Эта программа продолжит выполняться (получит процессорное время) только при появлении нового коннекта на порт 2222. Во всех остальных случаях программа никогда не получит процессорное время: она даже не будет требовать его от ОС и, следовательно, не будет влиять на load avarage (далее LA), потребляя только память.

С другой консоли запускаем первого клиента:

Открываем второе соединение:

По содержимому файла видно, что пришло только первое сообщение от первого клиента. Но при этом второе сообщение мы уже отправили, и оно где-то находится. Все сетевые подключения осуществляет ОС, значит и сообщение test client 2 сейчас в буфере операционной системы, в памяти, которая нам недоступна. Единственный способ забрать эти данные – обработать новое соединение командой accept, затем вызвать read.

Попробуем что-нибудь написать в первом клиенте:

Новое сообщение не попало в лог. Это происходит из-за того, что мы вызываем команду read только один раз, следовательно, в лог попадает только первое сообщение.

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

В этот момент наша программа запускает по циклу следующий accept и read, следовательно, принимает сообщение из второго соединения:

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

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

Компилируем и запускаем:

Проверяем, что получилось:

На этот раз все хорошо, мы забрали все данные, но проблема осталась: два соединения обрабатываются последовательно, по очереди, а это не подходит под наши требования. Если мы закроем первое соединение (ctrl + ]), то данные из второго соединения попадут сразу в лог:

Данные пришли. Но как обработать два соединения параллельно? Тут нам на помощь приходит команда fork. Что делает системный вызов fork в linux? Правильный ответ на этот вопрос на любом собеседовании – ничего. Fork – устаревший вызов, и в linux присутствует только для обратной совместимости. На самом деле, вызывая команду fork, вы вызываете системный вызов clone. Функция clone создает копию процесса и ставит оба процесса в очередь на процессор. Разница между ними в том, что fork копирует данные (переменные, буферы и т.п.) сразу в область памяти дочернего процесса, а clone копирует данные в дочерний процесс только при попытке их изменить (смотрите ограничения прав доступа к памяти в MMU). То есть, если вы вызываете fork 10 раз, а данные используете только для чтения, то вы получите 10 одинаковых копий данных в памяти. И это явно не то, что вам нужно, особенно в мультитредовых приложениях. Clone запускает копию вашего приложения, но не копирует данные сразу. Если вы запустите clone 10 раз, то у вас будет 10 исполняемых процессов с одним блоком памяти, и память будет копироваться только при попытке ее изменить дочерним процессом. Согласитесь, второй алгоритм намного эффективней.

В этой программе все то же самое — мы делаем accept, принимаем новое соединение. Далее мы запускаем fork. И если это мастер процесс (fork вернул pid созданного процесса), то мы закрываем текущее соединение в родительском процессе (оно доступно и в родителе, и в дочернем процессе). Если это дочерний процесс (fork вернул 0), то мы начинаем делать read с открытого сокета, который мы открыли командой accept в родительском процессе. По факту получается, что родительский процесс у нас только принимает соединения, а read/write мы делаем в дочерних процессах.

Компилируем и запускаем:

Очищаем наш лог файл:

Мы не закрываем оба соединения и можем туда еще что-то дописывать, смотрим наш лог:

Два соединения обрабатываются одновременно — мы получили желаемый результат.

Программа работает, но недостаточно быстро. Она сначала принимает соединение, а только потом запускает команду fork, и соединение обрабатывает только один процесс. Возникает вопрос: могут ли несколько процессов в ОС Linux работать с одним и тем же tcp портом? Пробуем.

Как видите, программа все еще не сильно изменилась, мы просто запускаем fork по циклу. В данном случае мы создаем два дочерних процесса, а только потом в каждом из них делаем accept на прием нового соединения. Проверяем.

Компилируем и запускаем:

Смотрим, что у нас в процессах:

Мы еще не подключились ни одним клиентом, а программа уже два раза сделала fork. Что же сейчас происходит с системой? Для начала мастер процесс: он находится в замкнутом цикле и проверяет, надо ли форкать еще процессы. Если мы будем делать это без остановки, то, по сути, будем постоянно требовать от ОС процессорное время, так как наш цикл должен исполняться всегда. Это значит, что мы потребляем 100% одного ядра – в команде ps значение 98.0%. Это же можно увидеть в команде top:

Если мы подключимся командой strace к родителю, то ничего не увидим, так как наш процесс не вызывает никакие функции ядра:

Что делают дочерние процессы? Тут начинается самое интересное. Судя по коду, все они после форка должны висеть в состоянии accept и ожидать новых соединений с одного и того же порта, в нашем случае 2222. Проверяем:

На данный момент они не требуют от ОС процессорного времени и потребляют только память. Но вот в чем вопрос: кто из них примет мое соединение, если я сделаю telnet? Проверяем:

Мы видим, что процесс, который был создан раньше (с меньшим pid), обработал соединение первым, и теперь находится в состоянии read. Если мы запустим второй telnet, то наше соединение обработает следующий процесс. После того, как мы закончили работать с сокетом, мы можем его закрыть и перейти снова в состояние accept (я этого делать не стал, чтобы не усложнять программу).

Остается последний вопрос: что нам делать с родительским процессом, чтобы он не потреблял столько cpu и при этом продолжал работать? Нам нужно отдать время другим процессам в добровольном порядке, то есть «сказать» нашей ОС, что какое-то время cpu нам не нужно. Для этой задачи подойдет команда sleep 1: если вы ее раскомментируете, то увидите в strace примерно такую, повторяющуюся раз в секунду, картину:

Наш процесс будет получать процессор примерно раз в секунду или, по крайней мере, требовать его от ОС.

Если вы всё еще не понимаете, к чему эта длинная статья, то посмотрите на apache httpd работающий в режиме prefork:

Дочерние процессы в accept:

Мастер процесс с секундной паузой:

При старте httpd мастер процесс плодит дочерние процессы, это легко увидеть если запустить strace на мастер процесс в момент старта:
Запустим веб сервер с такими настройками:

Эти настройки говорят о том, что каждый дочерний процесс будет обрабатывать только один запрос, затем процесс будет убиваться. Минимальное количество процессов в accept равно 9 и максимальное равно 10.

Если запустить strace на мастер процесс в момент старта, то мы увидим как мастер вызывает clone до тех пор, пока не достигнет MinSpareServers.

rt_sigaction(SIGSEGV, <0x7f9991933c20, [], SA_RESTORER|SA_RESETHAND, 0x7f99901dd500>, NULL, 8) = 0
rt_sigaction(SIGBUS, <0x7f9991933c20, [], SA_RESTORER|SA_RESETHAND, 0x7f99901dd500>, NULL, 8) = 0
rt_sigaction(SIGABRT, <0x7f9991933c20, [], SA_RESTORER|SA_RESETHAND, 0x7f99901dd500>, NULL, 8) = 0
rt_sigaction(SIGILL, <0x7f9991933c20, [], SA_RESTORER|SA_RESETHAND, 0x7f99901dd500>, NULL, 8) = 0
rt_sigaction(SIGFPE, <0x7f9991933c20, [], SA_RESTORER|SA_RESETHAND, 0x7f99901dd500>, NULL, 8) = 0
rt_sigaction(SIGTERM, <0x7f999193de50, [], SA_RESTORER, 0x7f99901dd500>, NULL, 8) = 0
rt_sigaction(SIGWINCH, <0x7f999193de50, [], SA_RESTORER, 0x7f99901dd500>, NULL, 8) = 0
rt_sigaction(SIGINT, <0x7f999193de50, [], SA_RESTORER, 0x7f99901dd500>, NULL, 8) = 0
rt_sigaction(SIGXCPU, , NULL, 8) = 0
rt_sigaction(SIGXFSZ, , NULL, 8) = 0
rt_sigaction(SIGPIPE, , NULL, 8) = 0
rt_sigaction(SIGHUP, <0x7f999193de80, [HUP USR1], SA_RESTORER, 0x7f99901dd500>, NULL, 8) = 0
rt_sigaction(SIGUSR1, <0x7f999193de80, [HUP USR1], SA_RESTORER, 0x7f99901dd500>, NULL, 8) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f99918eeab0) = 13098
write(2, «[Wed Jan 25 13:24:39 2017] [noti». 114) = 114
wait4(-1, 0x7fffae295fdc, WNOHANG|WSTOPPED, NULL) = 0
select(0, NULL, NULL, NULL, <1, 0>) = 0 (Timeout)
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f99918eeab0) = 13099
wait4(-1, 0x7fffae295fdc, WNOHANG|WSTOPPED, NULL) = 0
select(0, NULL, NULL, NULL, <1, 0>) = 0 (Timeout)
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f99918eeab0) = 13100
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f99918eeab0) = 13101
wait4(-1, 0x7fffae295fdc, WNOHANG|WSTOPPED, NULL) = 0
select(0, NULL, NULL, NULL, <1, 0>) = 0 (Timeout)
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f99918eeab0) = 13102
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f99918eeab0) = 13103
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f99918eeab0) = 13104
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f99918eeab0) = 13105
wait4(-1, 0x7fffae295fdc, WNOHANG|WSTOPPED, NULL) = 0
select(0, NULL, NULL, NULL, <1, 0>) = 0 (Timeout)
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f99918eeab0) = 13106
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f99918eeab0) = 13107
wait4(-1, 0x7fffae295fdc, WNOHANG|WSTOPPED, NULL) = 0
select(0, NULL, NULL, NULL, <1, 0>) = 0 (Timeout)

Смотрим как стартует апач – для этого можно просто смотреть ps axuf | grep [h]ttp каждую секунду, сразу после старта.

[root@www /]# date; ps axuf | grep [h]ttp
Wed Jan 25 14:12:10 EST 2017
root 13342 2.5 0.4 271084 9384? Ss 14:12 0:00 /usr/sbin/httpd
apache 13344 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd

[root@www /]# date; ps axuf | grep [h]ttp
Wed Jan 25 14:12:11 EST 2017
root 13342 1.6 0.4 271084 9384? Ss 14:12 0:00 /usr/sbin/httpd
apache 13344 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13348 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd

[root@www /]# date; ps axuf | grep [h]ttp
Wed Jan 25 14:12:11 EST 2017
root 13342 2.0 0.4 271084 9384? Ss 14:12 0:00 /usr/sbin/httpd
apache 13344 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13348 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13352 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13353 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd

[root@www /]# date; ps axuf | grep [h]ttp
Wed Jan 25 14:12:12 EST 2017
root 13342 1.7 0.4 271084 9384? Ss 14:12 0:00 /usr/sbin/httpd
apache 13344 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13348 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13352 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13353 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13357 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13358 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13359 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13360 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd

[root@www /]# date; ps axuf | grep [h]ttp
Wed Jan 25 14:12:13 EST 2017
root 13342 1.4 0.4 271084 9384? Ss 14:12 0:00 /usr/sbin/httpd
apache 13344 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13348 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13352 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13353 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13357 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13358 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13359 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13360 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13364 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
apache 13365 0.0 0.2 271084 5232? S 14:12 0:00 _ /usr/sbin/httpd
[root@www /]#

Итак, у нас несколько дочерних процессов, которые готовы принять наш http запрос. Давайте попробуем послать запрос:

Апач нам ответил 403, смотрим процессы:

Как видим, процесс с минимальным pid обработал запрос и завершил свою работу:

Дочерних процессов у нас осталось 9, что вписывается в наш лимит MinSpareServers.

Пробуем опять отправить запрос:

На этот раз наш запрос обработал процесс

так как теперь у него минимальный pid.

Но у нас осталось 8 свободных дочерних процессов в accept, одного не хватает до MinSpareServers, поэтому мастер процесс нам создал новый процесс:

Давайте скажем нашей ОС, чтобы она не давала процессорное время мастер процессу апача:

Статус процесса изменился, теперь он не работает.

Проверяем, работает ли у нас веб сервер:

О да, все еще работает, веб сервер еще отвечает.

Смотрим что у нас с процессами:

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

Естественно дочерних процессов у нас осталось 8, так как новый 9й плодить некому, мастер остановлен.

Давайте для эксперимента отправим еще один http запрос:

Логично, что ситуация повторяется.

Давайте скажем нашей ОС, что мастер процесс может снова продолжить работу:

Мастер процесс тут же считал exit code дочерних процессов, и упоминания о них ушли из таблицы процессов, а недостающие процессы мастер процесс нам снова склонировал — теперь у нас 10 свободных процессов в accept, что уместилось в рамки наших переменных из конфигов.

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

Этот код выглядит чуть сложнее, чем предыдущие, но его довольно легко объяснить. Допустим, в процессе нужно обрабатывать максимум 30 соединений. Мы создаем массив из нулей. Как только к нам придет новое соединение, мы его обрабатываем, а адрес сокета записываем в этот массив. Перебирая весь массив и все наши сокеты, мы можем последовательно считывать с них информацию. Но как нам узнать о новом соединении без использования вызова accept? В linux для этого есть как минимум 3 функции: select, poll и epoll. А в freebsd для этого есть аналог функции epoll под названием kqueue (kernel queue). Что делают эти команды? select – самая старая функция, которая до сих пор используется для того, чтобы отдавать всё процессорное время ядру, запрашивая его только при определенных условиях (по аналогии с accept). Разница в том, что ядро вернет нам cpu, когда на указанных нами сокетах начнется любая активность. Так как при запуске программы открыт только один сокет, то и в select мы указываем один. Если мы подключимся телнетом к нашему демону, то в select мы должны указывать уже два сокета: мастер сокет на порт 2222 и тот, который к нам подключился. Чтобы было понятней, продемонстрирую:

В этот момент мы с другой консоли делаем telnet на порт 2222 в наш демон и смотрим на трейс:

Сначала команде select мы указывали сокет 4 (смотрите в квадратных скобках). По /proc мы узнали, что 4й файл-дескриптор — это сокет с номером 42651147. По netstat мы узнали, что сокет с таким номером — это наш сокет в состоянии listen порта 2222. Как только мы подключились к этому сокету, ОС произвела tcp handshake с нашим telnet клиентом и установила новое соединение, о чем известила приложение через select. Наша программа получила процессорное время и начала обрабатывать пустой массив с соединениями. Увидев, что это новое соединение, мы запустили команду accept, зная, что она точно не заблокирует выполнение программы, так как соединение уже присутствует. То есть фактически мы используем тот же accept, только в неблокирующем режиме.

После того, как мы выполнили соединение, мы снова отдали управление ядру linux, но сказали ему, что теперь мы хотим получать уведомление по двум сокетам — под номером 4 и 5, что очень хорошо видно в команде strace ([4 5]). Именно так работает nginx: он способен обрабатывать большое количество сокетов одним процессом. По существующим сокетам мы можем проводить операции read/write, по новым можем вызывать accept. Select — очень старый системный вызов, имеющий ряд ограничений: например, на максимальное количество коннектов (файл дескрипторов). Ему на смену пришел сначала более совершенный системный вызов poll, лишенный этих лимитов и работающий быстрее. Впоследствии появились epoll и kqueue (в freebsd). Более современные функции позволяют более эффективно работать с коннектами.

Какие из этих функций поддерживает nginx? Nginx умеет работать со всеми этими функциями.

Ссылка на документацию. В этой статье я не буду описывать, чем отличаются все эти функции, т.к. объем текста уже достаточно большой.

Nginx использует fork для того, чтобы создавать процессы и загружать все ядра на сервере. Но каждый отдельно взятый дочерний процесс nginx работает с множеством соединений так же, как в примере с select, только использует для этого современные функции (для linux по умолчанию это epoll). Смотрим:

Что делает родительский мастер процесс nginx?

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

Все взаимодействие между процессами nginx осуществляет через unix сокеты:

Перед тем как выбирать те или иные инструменты важно понимать, как именно они работают. Так в некоторых случаях выгоднее использовать только apache httpd без nginx – и наоборот. Но чаще всего эти продукты используются вместе, потому что распараллеливанием обработки сокетов в апаче занимается ОС (разные процессы), а распараллеливанием обработки сокетов в nginx занимается сам nginx.

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

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *