Связка Nginx + Пускач + FastCGI на PHP
PHP — очень простой в освоении язык. По-видимому, изначально он был предназначен для вставки небольших динамических вычисляемых элементов в HTML-код страницы. Сильно упрощен старт разработки на РНР: поддержка языка включена в популярный web-сервер Apache; в PHP имеется библиотека функций для работы с популярной СУБД MySQL. Связка Apache, PHP, MySQL стала настолько распространенной, что многие начинающие разработчики даже не представляют, что можно жить по-другому. Из-за своей простоты PHP особенно популярен у начинающих web-программистов, которые со временем вырастают и начинают городить на PHP большие проекты.
С ростом нагрузки начинает медленно ворочаться тяжелый Apache, на который можно нацепить модулей больше, чем навесного инвентаря на трактор Беларусь, хотя это и удивительно, ведь задача web-сервера - реализация уж двадцать лет не менявшегося протокола HTTP. Начинают медленно ползать интерпретирующиеся каждый раз PHP скрипты с тучей подключаемых модулей. Для лечения архитектурных болезней изобретаются новые костыли: оптимизируется повторная загрузка PHP-кода; на отдачу статического содержимого ставился более легкий web-сервер, который ретранслирует запросы к скриптам на стоящий «позади» Apache.
В качестве решения предлагается запускать PHP-скрипты как службы под управлением Пускача и связывать их с легким web-сервером Nginx по унифицированному интерфейсу FastCGI. Такой подход исключит следующие накладные расходы: web-сервер, медленно отдающий статическое содержимое сайта; многократную повторную загрузку и интерпретацию кода скрипта; теоретически позволит однократно выполнять инициализацию в логике самого скрипта. При этом будет обеспечена достаточная стабильность и надежность работы (ведь ошибка в работе службы приводит к более тяжелым последствиям, чем ошибка при обработке одного запроса).
Конфигурация Nginx
Ниже приведена минимальная конфигурация Nginx для работы с PHP в режиме FastCGI. Все запросы к исполняемым скриптам направляются на UNIX / сетевой сокет, указанный в fastcgi_pass
. Этот же сокет будет слушать сервер приложений Пускач, он будет принимать все запросы и направлять их на обработку целевым скриптам, определяя их по переменной SCRIPT_FILENAME
.
- events { }
- http
- {
- server
- {
- listen host:port;
- access_log /usr/home/site_user/logs/access.log;
- error_log /usr/home/site_user/logs/errors.log;
- ssi on;
- root /usr/home/site_user/www;
- index index.html index.html index.fcgi index.php;
- location ~ \.(fpl|fcgi|php|fphp)$
- {
- fastcgi_pass unix:/usr/home/site_user/tmp/puskach.sock;
- }
- fastcgi_index index.fcgi index.php;
- fastcgi_param SCRIPT_FILENAME /usr/home/site_user/www$fastcgi_script_name;
- fastcgi_param DOCUMENT_ROOT /usr/home/site_user/www;
- # Стандартные параметры интерфейса FastCGI, поставляются с Nginx
- include "/usr/local/nginx/conf/fastcgi_params";
- }
- }
Конфигурация Пускача
Пускач — это сервер приложений, который принимает запросы от web-сервера и распределяет их между целевыми скриптами, определяя скрипт по переменной SCRIPT_FILENAME
, которую обязательно должен передавать web-сервер. Для каждого скрипта создается пул копий (процессов-обработчиков запросов), настройки пула берутся из соответствующего блока fcgi
, либо используется конфигурация по умолчанию default_fcgi
. Все параметры конфигурации и аргументы командной строки Пускача описаны в документации.
PHP-скрипт, в отличие от бинарного FastCGI-приложения или Perl-скрипта, является текстовым файлом, то есть не распознается как исполняемый командным интерпретатором. Для возможности запуска таких скриптов в конфигурации Пускача необходимо описать секцию start_cmd
. Команда cmd
будет выполнена для запуска скриптов с масками *.php
и *.fphp
. Для выполнения дополнительных действий (установка переменных окружения, смена текущей директории и т.д.) в качестве cmd
можно указывать любой исполняемый файл, в том числе sh или perl-скрипт.
- req_pool_size 256
- # здесь /php-ini/dir/ - каталог, содержащий php.ini
- start_cmd
- {
- mask *.php
- mask *.fphp
- cmd "/usr/bin/php-cgi -c /php-ini/dir/ -f $script_filename"
- }
- default_fcgi
- {
- unix_socket_prefix "/usr/home/site_user/tmp/f"
- io_timeout 10000
- sleep_timeout 30000
- request_processing_timeout 15000
- max_processes 5
- requests_per_process 1000
- }
- fcgi
- {
- path "/usr/home/site_user/www/upload_file.fcgi"
- unix_socket_prefix "/usr/home/site_user/tmp/f"
- io_timeout 30000
- sleep_timeout 30000
- request_processing_timeout 3600000
- max_processes 25
- requests_per_process 1000
- }
Как писать PHP-скрипт
FastCGI — это интерфейс для работы со службой, обрабатывающей запросы. Служба замечательна тем, что сохраняет состояние между обработкой запросов, то есть не обрабатывает каждый запрос с чистого листа. Такой подход теоретически позволяет однократно выполнить интерпретацию скрипта и загрузку в память необходимых модулей. Особенность текущей реализации различных интерфейсов (Apache mod_php, CGI, FastCGI) для PHP в том, что код скрипта не меняется под каждый интерфейс, из-за чего сохраняется совместимость и появляется относительная свобода в выборе интерфейса, но исключается возможность использования преимуществ отдельного интерфейса.
- <?php
- echo "Мой PID: " . getmypid();
- ?>
Если несколько раз запустить приведенный скрипт, он напечатает один и тот же PID. В результате за счет отказа от тяжелого web-сервера и от многократного перезапуска скрипта будет получен определенный выигрыш в производительности. Правда основной потенциал интерфейса при этом не задействуется, и связано это с архитектурной кривизной PHP. Практически в любом скрипте есть ресурсы и данные, которые можно использовать многократно для обработки разных запросов: соединение с СУБД, подготовленные к выполнению SQL-запросы, загруженные данные конфигурационных файлов и т.д. В реализации FastCGI-интерфейса для языков Perl, С и других эти ресурсы выносятся за цикл обработки запросов, инициализируются однократно и переиспользуются. В PHP же все эти данные теряются и для обработки следующего запроса нужно опять устанавливать подключение к базе данных, загружать настройки из файлов и т.д.
В сети существуют половинчатые (и в плане архитектуры, и в плане работоспособности) решения этой проблемы, смысл которых — реализация совмещенного со скриптом сервера приложений на PHP. То есть скрипт сам внутри себя организует цикл обработки запросов, как и положено, работая в режиме службы. Недостаток такого подхода — нестандратность решения и необходимость модификации скриптов. Изменяя скрипты, приходится завязываться на чудную, нефункциональную, неоттестированную и редкую реализацию. В случае обнаружения ошибок, например, с ростом нагрузки, придется проводить болезненную процедуру по откату скриптов к классическому виду, либо самому дорабатывать решение напильником.
Хорошим решением тут была бы правильная простая бинарная реализация интерфейса FastCGI, возможно, с внесением изменений в интерпретатор PHP. При должном интересе к этой теме возможно будет дан старт таких исследований. В любом случае переход на FastCGI для PHP — дело стоящее и обещающее в будущем увеличение производительности.