Производительность Nginx vs Apache: методы оптимизации
Apache был выпущен в 1995 году, и, согласно Википедии, «сыграл ключевую роль в развитии Всемирной паутины». На сегодняшний день это все еще наиболее часто используемое программное обеспечение для работы серверов. Но наблюдается тенденция снижения доли рынка Apache и роста Nginx.
Nginx был выпущен в 2004 году. Сначала он использовался в качестве дополнения к Apache. Но его популярность неуклонно растет.
Apache существует уже давно, и для него доступен большой набор модулей. Известно, что управление серверами Apache удобно для пользователя. Динамическая загрузка модулей позволяет компилировать и добавлять различные модули в стек Apache без перекомпиляции двоичного файла главного сервера.
Подобная гибкость еще не доступна для Nginx. Из руководства по настройке Nginx для HTTP/2 становится ясно, что его модули настраиваются во время сборки.
Еще одна особенность, которая обеспечила Apache доминирование на рынке веб-серверов, это файл .htaccess. Он позволяет контролировать конфигурацию сервера на уровне каталогов. Каждый каталог на сервере, обслуживаемом Apache, может иметь свой собственный файл .htaccess.
Nginx не только не имеет эквивалентного решения, но и препятствует его внедрению из-за снижения производительности.

Доли рынка для различных серверов в 1995–2005 гг.
LiteSpeed - это один из претендентов, который можно сравнить с Apache. Но он лишен проблем с производительностью.
Как и Apache он поддерживает .htaccess, mod_security и mod_rewrite. LiteSpeed был задуман как замена Apache. Он работает с cPanel и Plesk, с 2015 года поддерживает HTTP/2.
Это делает LiteSpeed серьезным конкурентом, ориентированным на хостинг-провайдеров, уделяющих внимание производительности. Но он также может использоваться для небольших серверов или сайтов.
Вопросы аппаратного обеспечения
Какое бы из этих решений мы ни выбрали, наличие достаточного объема ОЗУ имеет большое значение. Когда процесс веб-сервера не имеет достаточного объема оперативной памяти, он начинает использовать файл подкачку. Он подразумевает использование жесткого диска или SSD для расширения оперативной памяти. В результате увеличивается время ожидания при каждом обращении к этой памяти.
Мониторинг
Htop является одним из эффективных способов мониторинга текущей производительности стека серверов.

Другие инструменты мониторинга: New Relic, Netdata. Последнее решение может отправлять предупреждения для любого приложения или системного процесса по электронной почте, через Slack, pushbullet, Telegram, Twilio и т. д.

Monit - еще один инструмент с открытым исходным кодом, который позволяет контролировать систему. В нем можно настроить отправку оповещений, перезапуск определенных процессов или перезагрузку системы при выполнении заданных условий.
Тестирование системы
AB (Apache Benchmark) - простой инструмент тестирования нагрузки от Apache Foundation. Еще одна программа для тестирования - Siege. Также доступен инструмент на основе Python - Locust.

После установки Locust нужно будет создать файл locust в каталоге, из которого вы запускаете приложение:
from locust import HttpLocust, TaskSet, task
class UserBehavior(TaskSet):
@task(1)
def index(self):
self.client.get("/")
@task(2)
def shop(self):
self.client.get("/?page_id=5")
@task(3)
def page(self):
self.client.get("/?page_id=2")
class WebsiteUser(HttpLocust):
task_set = UserBehavior
min_wait = 300
max_wait = 3000
Затем запускаем инструмент из командной строки:
locust --host=https://my-website.com
Применение перечисленных выше средств создает эффект DDoS-атаки, поэтому рекомендуется ограничить тестирование собственными сайтами.
Настройка Apache
MPM-модули Apache
Вначале своего существования Apache создавал новый процесс для каждого входящего TCP-соединения и ответа на него. Если появлялись другие соединения, создавались другие рабочие процессы для их обработки.
Чтобы снизить затраты ресурсов, разработчики Apache создали режим prefork с заранее заданным числом процессов. Но встроенные динамические языковые интерпретаторы в каждом процессе все равно потребляли много ресурсов, Поэтому сбои в работе серверов Apache с настройками по умолчанию стали распространенным явлением.
Эта модель известна как mpm_prefork_module . Данный режим требует небольшой настройки. При этом значение, задаваемое директивой MaxRequestWorkers, должно быть достаточно большим, чтобы обрабатывать столько одновременных запросов, сколько вы ожидаете получить. Но достаточно малым, чтобы обеспечить необходимый объем физической памяти для всех процессов.

Тест Locust, который показывает порождение огромного числа процессов Apache для обработки входящего трафика.
Этот режим является основной причиной плохой репутации Apache. Его использование может привести к неэффективному распределению ресурсов.
Версия Apache 2 реализовала еще два MPM, которые пытались решить проблемы, связанные с режимом prefork. Это модуль worker (mpm_worker_module) и модуль событий.
Модуль worker реализует гибридный режим работы, основанный на потоке процессов. Согласно официальному сайту Apache:
Родительский процесс управления отвечает за запуск дочерних процессов. Каждый из них создает фиксированное число потоков, указанное в директиве ThreadsPerChild. А также поток, который прослушивает соединения и передает их потоку сервера для обработки, когда они поступают.
Этот режим использует ресурсы более эффективно.
В версии Apache 2.4 появился модуль событий. Он добавляет отдельный поток прослушивателя, который управляет бездействующими соединениями после завершения HTTP-запроса. Это неблокирующий асинхронный режим, потребляющий меньший объем памяти.
Мы загрузили тестовую установку WooCommerce с 1200 записями на виртуальном сервере и протестировали ее на Apache 2.4 со стандартным режимом, prefork и mod_php.
Сначала мы протестировали его в libapache2-mod-php7 и mpm_prefork_module на https://tools.pingdom.com:

Затем мы провели тестирование MPM модуля событий. Нам нужно было добавить multiverse в /etc/apt/sources.list
:
deb http://archive.ubuntu.com/ubuntu xenial main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu xenial-updates main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu xenial-security main restricted universe multiverse
deb http://archive.canonical.com/ubuntu xenial partner
После этого мы выполнили sudo apt-get update
и установили libapache2-mod-fastcgiphp-fpm
:
sudo apt-get install libapache2-mod-fastcgi php7.0-fpm
Поскольку php-fpm - это служба, отдельная от Apache, требуется выполнить ее перезапуск:
sudo service start php7.0-fpm
Затем мы отключили модуль prefork, включили режим событий и proxy_fcgi:
sudo a2dismod php7.0 mpm_prefork
sudo a2enmod mpm_event proxy_fcgi
После чего добавили следующий фрагмент кода к виртуальному хосту Apache:
<filesmatch ".php$">
SetHandler "proxy:fcgi://127.0.0.1:9000/"
</filesmatch>
Указанный порт должен соответствовать конфигурации php-fpm в /etc/php/7.0/fpm/pool.d/www.conf
. Более подробно о настройке php-fpm можно узнать здесь.
Затем мы настроили конфигурацию mpm_event /etc/apache2/mods-available/mpm_event.conf
. Подробнее о каждой директиве для сайта на Apache и
Директива MaxRequestWorkers устанавливает ограничение на количество одновременных запросов. Значение, не равное нулю, предотвращает возможную утечку памяти.
<ifmodule mpm_event_module>
StartServers 1
MinSpareThreads 30
MaxSpareThreads 75
ThreadLimit 64
ThreadsPerChild 30
MaxRequestWorkers 80
MaxConnectionsPerChild 80
</ifmodule>
Затем мы перезапустили сервер с помощью sudo service apache2 restart
. Тестирование на Pingdom показало, что время загрузки страницы сократилось вдвое:

Другие советы по настройке Apache
Отключение .htaccess: Этот файл позволяет создать конфигурацию для каждого отдельного каталога в корне сервера без перезагрузки. Таким образом, обход всех каталогов, поиск файлов .htaccess при каждом запросе влечет за собой снижение производительности.
Выдержка из документации Apache:
Файлы .htaccess
следует использовать только в том случае, если у вас нет доступа к основному файлу конфигурации сервера. Любые настройки конфигурации .htaccess
, могут быть также выполнены в разделе <directory>
файла конфигурации основного сервера.
Решение заключается в том, чтобы отключить этот файл в /etc/apache2/apache2.conf
:
AllowOverride None
Если нужно использовать .htaccess для конкретных каталогов, можно включить его в разделы виртуального хоста:
AllowOverride All
Дополнительные советы:
- Управляйте кешем браузера с помощью mod_expires- устанавливая заголовки expires.
- Оставьте HostNameLookups выключенным -
HostNameLookups Off
,это значение, используемое по умолчанию. - Apache2buddy- скрипт, который можно запустить и получить советы по настройке системы:
curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl | perl

Nginx
Это сервер, работающий на основе событий. Переход на Nginx часто является решением многих проблем с производительностью сайта или сервера.

Более подробное визуальное пояснение архитектуры Nginx можно найти здесь.
Настройки Nginx
Nginx рекомендует привязывать количество потоков workers к количеству ядер процессора, установив в /etc/nginx/nginx.conf
для worker_processes значение auto
(по умолчанию используется значение 1).
worker_connections
устанавливает количество соединений, которые может обработать каждый процесс worker
. По умолчанию - 512, но это значение можно увеличить.
Keepalive-соединения также влияет на производительность.

Согласно официальному сайту Nginx:
HTTP keepalive-соединения - это необходимая функция производительности, которая снижает задержку и позволяет быстрее загружать веб-страницы.
Установление новых TCP-соединений может быть ресурсоемким. Протокол HTTP/2 частично решает эту проблему благодаря функциям мультиплексирования. Повторное использование существующего соединения может сократить время обработки запроса.
Workers Nginx могут обрабатывать тысячи входящих соединений одновременно. Если Nginx применяется в качестве обратного прокси-сервера, то он использует локальный пул keepalive-соединений без издержек TCP-соединения.
Параметр keepalive_requests устанавливает количество запросов, которые клиент может выполнить через одно keepalive-соединение.
Параметр keepalive_timeout
устанавливает время, в течение которого keepalive- соединение остается открытым.
Включение входящих keepalive-соединений требует помещения этих директив в основную конфигурацию Nginx:
proxy_http_version 1.1;
proxy_set_header Connection "";
Они управляются модулем ngx_http_upstream_module.
Если интерфейсное приложение продолжает опрашивать фоновое приложение о наличии обновлений, увеличение значений keepalive_requests
и keepalive_timeout
ограничит число соединений, которые необходимо установить.
Значение директивы keepalive
не должно быть слишком большим, чтобы позволить другим соединениям достигнуть вышестоящего сервера.
Использование unix сокетов
По умолчанию Nginx использует отдельный процесс PHP, в который он перенаправляет запросы PHP-файлов. В этом он действует как прокси.
Установка виртуального хоста с Nginx будет выглядеть так:
location ~ .php$ {
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9000;
}
FastCGI - это протокол, отличный от HTTP. Поэтому первые две строки передают аргументы и заголовки в php-fpm. Третья строка указывает способ проксирования запроса - через сокет локальной сети.
Это удобно для многосерверных установок, поскольку мы можем указать удаленные серверы для проксирования запросов.
Но если мы размещаем всю установку в одной системе, нужно использовать Unix сокет для подключения к процессу прослушивания php:
fastcgi_pass unix:/var/run/php7.0-fpm.sock;
gzip_static: общепринятая рекомендация относительно производительности сервера заключается в необходимости сжатия статических ресурсов. Но нужно пытаться сжимать только файлы, размер которых превышает установленное значение. Так как сжатие ресурсов на лету при каждом запросе может быть ресурсозатратным.
Nginx предоставляет директиву gzip_static
, которая позволяет обслуживать сжатые версии файлов с расширением .gz вместо обычных ресурсов:
location /assets {
gzip_static on;
}
Таким образом, Nginx будет пытаться обслуживать style.css.gz
вместо style.css
. Поэтому мощности ЦП не будут тратиться на сжатие «на лету» для каждого запроса.
Кеширование с помощью Nginx
Включить кеширование в Nginx довольно просто.
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g
inactive=60m;
Эту директиву мы помещаем в файл виртуального хоста, вне блока server
. Аргумент proxy_cache_path
может быть любым путем, по которому сохраняется кэш.
levels
определяет количество уровней каталогов, в которых Nginx должен хранить кэшированное содержимое.
Аргумент keys_zone
- это имя общей зоны памяти, используемой для хранения ключей кэша.10m - это объем памяти, выделяемый
для этих ключей.
max_size
не является обязательным параметром и устанавливает верхний предел для размера кэшируемого содержимого. В нашем случае это 10 ГБ.
inactive
указывает, как долго содержимое может оставаться в кэше без запроса, прежде чем оно будет удалено Nginx.
После этого также следует указать строку с именем зоны памяти либо в блоке server
или в блоке location
:
proxy_cache my_cache;
Дополнительный уровень надежности достигается путем указания Nginx обслуживать элементы из кэша, когда он сталкивается с ошибкой сервера в источнике:
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
Более подробную информацию о директивах блоков server
и location для
Nginx можно найти здесь.
Директивы proxy_cache_*
предназначены для статических ресурсов. Но чаще нужно кэшировать динамический вывод веб-приложений. В этом случае мы будем использовать следующую директиву:
fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=my_cache:10m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
add_header NGINX_FASTCGI_CACHE $upstream_cache_status;
Последняя строка устанавливает заголовки ответа, чтобы сообщить нам, был ли контент доставлен из кэша или нет.
Затем в блоке server или location можно установить исключения для кэширования. Например, если строка запроса присутствует в URL-адресе:
if ($query_string != "") {
set $skip_cache 1;
}
Также в блоке server при использовании PHP мы добавляем следующий код:
location ~ .php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_read_timeout 360s;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
fastcgi_index index.php;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache my_cache;
fastcgi_cache_valid 60m;
}
Строки fastcgi_cache*
и fastcgi_no_cache
настраивают кэширование и исключения. Подробное описание этих директив можно найти в документации Nginx.
Заключение
Мы рассмотрели несколько методов улучшения производительности веб-сервера. Достижение наилучших результатов в Apache и Nginx осуществляется путем тестирования и анализа конкретных случаев. Поэтому Nginx или Apache это бесконечная тема.