Статьи

Оптимизация на стороне сервера с помощью Nginx и pm-static

Эта статья является частью серии по созданию примера приложения — блога галереи с несколькими изображениями — для оценки производительности и оптимизации. (Посмотреть репо здесь.)


Давайте продолжим оптимизацию нашего приложения . Мы начинаем с создания эскизов «на лету», которое занимает 28 секунд на запрос, в зависимости от платформы, на которой запущено ваше демонстрационное приложение (в моем случае это была медленная интеграция файловой системы между операционной системой хоста и Vagrant), и доводим ее до довольно приемлемо 0,7 секунды.

28 секунд для загрузки страницы

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

1,2 секунды на запрос

Исправление проблем

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

Примечание: если вы размещаете коробку Homestead Improved на компьютере с Windows, возможно, существует проблема с общими папками. Это можно решить, добавив параметр type: "nfs" в folder в Homestead.yaml :

настройка нфс

Вы также должны запускать vagrant up из интерфейса оболочки / powershell, который имеет административные привилегии, если проблемы сохраняются (щелкните правой кнопкой мыши, запустите от имени администратора).

В одном примере перед этим мы получали от 20 до 30 секунд времени загрузки на каждый запрос и не могли получить скорость быстрее, чем один запрос в секунду (она была ближе к 0,5 в секунду):

0,9 саранча рпс

Процесс

Давайте пройдем через процесс тестирования. Мы установили Locust на наш хост и создали очень простой locustfile.py :

 from locust import HttpLocust, TaskSet, task class UserBehavior(TaskSet): @task(1) def index(self): self.client.get("/") class WebsiteUser(HttpLocust): task_set = UserBehavior min_wait = 300 max_wait = 1000 

Затем мы загрузили ngrok на наш гостевой компьютер и туннелировали все HTTP-соединения через него, чтобы мы могли протестировать наше приложение по статическому URL.

Затем мы запустили Locust и наполнили наше приложение 100 параллельными пользователями:

саранча

Наш серверный стек состоял из PHP 7.1.10, Nginx 1.13.3 и MySQL 5.7.19 в Ubuntu 16.04.

PHP-FPM и его настройки диспетчера процессов

php-fpm порождает свои собственные процессы, независимые от процесса веб-сервера. Управление количеством этих процессов настраивается в /etc/php/7.1/fpm/pool.d/www.conf (здесь 7.1 можно заменить на действительный номер версии PHP, который используется в настоящее время).

В этом файле мы находим настройку pm . Этот параметр может быть установлен на dynamic , ondemand и static . Динамическая, возможно, самая распространенная мудрость; это позволяет серверу манипулировать количеством порожденных процессов PHP между несколькими настройками:

 pm = dynamic ; The number of child processes to be created when pm is set to 'static' and the ; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. ; This value sets the limit on the number of simultaneous requests that will be ; served. pm.max_children = 6 ; The number of child processes created on startup. ; Note: Used only when pm is set to 'dynamic' ; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2 pm.start_servers = 3 ; The desired minimum number of idle server processes ; Note: Used only when pm is set to 'dynamic' ; Note: Mandatory when pm is set to 'dynamic' pm.min_spare_servers = 2 ; The desired maximum number of idle server proceses ; Note: Used only when pm is set to 'dynamic' ; Note: Mandatory when pm is set to 'dynamic' pm.max_spare_servers = 4 

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

После исправления проблемы с общими папками Windows с помощью nfs и тестирования с помощью Locust мы смогли получать примерно пять запросов в секунду с ошибками около 17–19% при 100 одновременных пользователях. Как только он был заполнен запросами, сервер замедлялся, и каждый запрос выполнялся более десяти секунд.

Затем мы изменили настройку pm на ondemand .

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

 pm.max_children = 6 ; and pm.process_idle_timeout = 20s; ; The number of seconds after which an idle process will be killed. ; Note: Used only when pm is set to 'ondemand' ; Default Value: 10s 

При тестировании мы немного увеличили эти настройки, меньше заботясь о ресурсах.

Также есть pm.max_requests , который можно изменить и который определяет количество запросов, которые каждый дочерний процесс должен выполнить перед повторным вызовом.

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

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

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

Мы увидели улучшение на 20%. Тем не менее, количество неудавшихся запросов было все еще значительным, а время ответа было не очень хорошим. Система была далеко не готова к производству.

Саранча - вечера в статике

Однако в Pingdom Tools мы получили сносные 3,48 секунды, когда система не находилась под давлением:

вечера статические - пингдом

Это означало, что pm static был улучшением, но в случае большей нагрузки он все равно падал.

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

И нам это удалось. Посмотрим как.

Nginx и fastcgi Кэширование

 proxy_cache_path /home/vagrant/Code/ng-cache levels=1:2 keys_zone=ng_cache:10m max_size=10g inactive=60m; proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504; fastcgi_cache_path /home/vagrant/Code/ngd-cache levels=1:2 keys_zone=ngd_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 { listen 80; listen 443 ssl http2; server_name nginx-performance.app; root "/home/vagrant/Code/project-nginx/public"; index index.html index.htm index.php; charset utf-8; proxy_cache ng_cache; location / { try_files $uri $uri/ /index.php?$query_string; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } access_log off; error_log /var/log/nginx/nginx-performance.app-error.log error; sendfile off; client_max_body_size 100m; location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/var/run/php/php7.1-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_intercept_errors off; fastcgi_buffer_size 16k; fastcgi_buffers 4 16k; fastcgi_connect_timeout 300; fastcgi_send_timeout 300; fastcgi_read_timeout 300; fastcgi_cache ngd_cache; fastcgi_cache_valid 60m; } location ~ /\.ht { deny all; } ssl_certificate /etc/nginx/ssl/nginx-performance.app.crt; ssl_certificate_key /etc/nginx/ssl/nginx-performance.app.key; } 

Мы открыли наш файл виртуального хоста Nginx и добавили вышеуказанные настройки. Давайте объясним их.

 proxy_cache_path /home/vagrant/Code/ng-cache levels=1:2 keys_zone=ng_cache:10m max_size=10g inactive=60m; 

Как объясняется в proxy_cache_path Apache против производительности Nginx: методы оптимизации» , proxy_cache_path используется для кэширования статических ресурсов — таких как изображения, таблицы стилей, файлы JavaScript. Сам путь должен существовать; нам нужно создать эти каталоги. levels обозначает глубину каталогов внутри этого пути / папки. Обход может быть дорогостоящим для времени запроса, поэтому хорошо держать его небольшим. Зона ключей это имя; каждый виртуальный хост может (и должен) использовать отдельный. Максимальный размер означает максимальный размер кэша, а неактивный означает, что элементы времени будут храниться в кэше, даже если они не запрошены.

После этого времени бездействия кэш для ресурса будет снова заполнен.

proxy_cache_use_stale и fastcgi_cache_use_stale интересны, поскольку они могут предоставить функцию «всегда в сети», которую мы можем видеть с провайдерами CDN, такими как Cloudflare: если серверная часть отключается, Nginx будет обслуживать эти ресурсы из кэша. Это в некоторой степени защищает наш веб-сайт.

Все настройки fastcgi_cache_* предназначены для сгенерированного PHP (динамического) содержимого, а настройки proxy_cache_* — для статических файлов.

fastcgi_cache_key определяет ключ для кэширования.

fastcgi_ignore_headers отключает обработку некоторых полей заголовка ответа из бэкэнда FastCGI .

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

 fastcgi_cache_purge 

Это определяет запросы, которые смогут очистить кеш. Nginx (его ngx_http_fastcgi_module ) предоставляет нам достаточно полный набор инструментов для кэширования. Одним из примеров использования вышеуказанной директивы будет:

 fastcgi_cache_path /data/nginx/cache keys_zone=cache_zone:10m; map $request_method $purge_method { PURGE 1; default 0; } server { ... location / { fastcgi_pass backend; fastcgi_cache cache_zone; fastcgi_cache_key $uri; fastcgi_cache_purge $purge_method; } } 

Здесь запрос PURGE REST сможет удалить вещи из кеша.

Также возможно провести повторную проверку кэша при некоторых условиях.

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

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

 add_header NGINX_FASTCGI_CACHE $upstream_cache_status; 

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

Заголовки кэша Nginx

Чтобы прогреть кеш, нам нужно пройти запросы к каждому из ресурсов.

fastcgi_cache_methods может быть полезен для кэширования определенных методов запроса, таких как POST. GET и HEAD кэшируются по умолчанию.

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

Можно легко спроектировать целую частную сеть CDN со всеми возможностями конфигурации, которые предлагает Nginx.

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

Nginx кэш роится

Мы можем видеть, что среднее время на запрос составило 170 миллисекунд. Это примерно стократное улучшение. Количество запросов в секунду было выше 100.

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

Кэширование в Nginx принесло нам значительные улучшения. Основным узким местом в этом приложении не будут аппаратные ресурсы, даже если они скромные.

Терминал Caching Locust

Также видно, что процент неудачных запросов увеличился с 17% до 0,53%.

Затем мы пошли на страницу теста Pingdom и протестировали наш сайт:

GTmetrix

Мы видим, что нам удалось довести время загрузки страницы намного ниже одной секунды!

Мы также протестировали одну страницу галереи, в которой есть дополнительный «багаж» связанных и новейших галерей:

Тест страницы одной галереи

Мы прилагаем отчет HAR файла этого теста для анализа.

Вывод

В этой статье были проверены некоторые моменты, затронутые в моем предыдущем обсуждении производительности Nginx , а также обсуждены и проанализированы другие параметры, такие как управление процессом и мера его влияния на время загрузки страницы.

Мы что-то упустили, стоит упомянуть? Можете ли вы вспомнить другие настройки Nginx, которые мы могли бы применить к этому приложению для повышения производительности?