Эта статья является частью серии по созданию примера приложения — блога галереи с несколькими изображениями — для оценки производительности и оптимизации. (Посмотреть репо здесь.)
Давайте продолжим оптимизацию нашего приложения . Мы начинаем с создания эскизов «на лету», которое занимает 28 секунд на запрос, в зависимости от платформы, на которой запущено ваше демонстрационное приложение (в моем случае это была медленная интеграция файловой системы между операционной системой хоста и Vagrant), и доводим ее до довольно приемлемо 0,7 секунды.
Следует признать, что эти 28 секунд должны происходить только при начальной загрузке. После тюнинга мы смогли добиться готовности к производству:
Исправление проблем
Предполагается, что вы прошли процесс начальной загрузки и на вашем компьютере запущено приложение — виртуальное или реальное.
Примечание: если вы размещаете коробку Homestead Improved на компьютере с Windows, возможно, существует проблема с общими папками. Это можно решить, добавив параметр type: "nfs"
в folder
в Homestead.yaml
:
Вы также должны запускать vagrant up
из интерфейса оболочки / powershell, который имеет административные привилегии, если проблемы сохраняются (щелкните правой кнопкой мыши, запустите от имени администратора).
В одном примере перед этим мы получали от 20 до 30 секунд времени загрузки на каждый запрос и не могли получить скорость быстрее, чем один запрос в секунду (она была ближе к 0,5 в секунду):
Процесс
Давайте пройдем через процесс тестирования. Мы установили 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;
Затем мы можем проверить и проанализировать время загрузки нашей страницы, чтобы увидеть, что работает, а что нет:
Чтобы прогреть кеш, нам нужно пройти запросы к каждому из ресурсов.
fastcgi_cache_methods
может быть полезен для кэширования определенных методов запроса, таких как POST. GET и HEAD кэшируются по умолчанию.
Существует также кэширование в байтовом диапазоне, которое можно использовать для оптимизации потокового видео, как описано здесь .
Можно легко спроектировать целую частную сеть CDN со всеми возможностями конфигурации, которые предлагает Nginx.
Включив вышеуказанную конфигурацию — как для статического, так и для динамического содержимого нашего веб-сайта — мы запустили Locust и наполнили нашу систему 100 параллельными пользователями. Разница в результатах была просто удивительной. Напряжение, на котором сервер находился ранее, теперь не ощущалось.
Мы можем видеть, что среднее время на запрос составило 170 миллисекунд. Это примерно стократное улучшение. Количество запросов в секунду было выше 100.
На графике среднего времени отклика также видно, что в начальных запросах время отклика резко возросло, а после этого время отклика все больше и больше уменьшалось до примерно 130 мс.
Кэширование в Nginx принесло нам значительные улучшения. Основным узким местом в этом приложении не будут аппаратные ресурсы, даже если они скромные.
Также видно, что процент неудачных запросов увеличился с 17% до 0,53%.
Затем мы пошли на страницу теста Pingdom и протестировали наш сайт:
Мы видим, что нам удалось довести время загрузки страницы намного ниже одной секунды!
Мы также протестировали одну страницу галереи, в которой есть дополнительный «багаж» связанных и новейших галерей:
Мы прилагаем отчет HAR файла этого теста для анализа.
Вывод
В этой статье были проверены некоторые моменты, затронутые в моем предыдущем обсуждении производительности Nginx , а также обсуждены и проанализированы другие параметры, такие как управление процессом и мера его влияния на время загрузки страницы.
Мы что-то упустили, стоит упомянуть? Можете ли вы вспомнить другие настройки Nginx, которые мы могли бы применить к этому приложению для повышения производительности?