Фальстарт

Связка 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.

  1. events { }
  2. http
  3. {
  4. server
  5. {
  6. listen host:port;
  7. access_log /usr/home/site_user/logs/access.log;
  8. error_log /usr/home/site_user/logs/errors.log;
  9. ssi on;
  10. root /usr/home/site_user/www;
  11. index index.html index.html index.fcgi index.php;
  12. location ~ \.(fpl|fcgi|php|fphp)$
  13. {
  14. fastcgi_pass unix:/usr/home/site_user/tmp/puskach.sock;
  15. }
  16. fastcgi_index index.fcgi index.php;
  17. fastcgi_param SCRIPT_FILENAME /usr/home/site_user/www$fastcgi_script_name;
  18. fastcgi_param DOCUMENT_ROOT   /usr/home/site_user/www;
  19. # Стандартные параметры интерфейса FastCGI, поставляются с Nginx
  20. include "/usr/local/nginx/conf/fastcgi_params";
  21. }
  22. }

Конфигурация Пускача

Пускач — это сервер приложений, который принимает запросы от web-сервера и распределяет их между целевыми скриптами, определяя скрипт по переменной SCRIPT_FILENAME, которую обязательно должен передавать web-сервер. Для каждого скрипта создается пул копий (процессов-обработчиков запросов), настройки пула берутся из соответствующего блока fcgi, либо используется конфигурация по умолчанию default_fcgi. Все параметры конфигурации и аргументы командной строки Пускача описаны в документации.

PHP-скрипт, в отличие от бинарного FastCGI-приложения или Perl-скрипта, является текстовым файлом, то есть не распознается как исполняемый командным интерпретатором. Для возможности запуска таких скриптов в конфигурации Пускача необходимо описать секцию start_cmd. Команда cmd будет выполнена для запуска скриптов с масками *.php и *.fphp. Для выполнения дополнительных действий (установка переменных окружения, смена текущей директории и т.д.) в качестве cmd можно указывать любой исполняемый файл, в том числе sh или perl-скрипт.

  1. req_pool_size 256
  2. # здесь /php-ini/dir/ - каталог, содержащий php.ini
  3. start_cmd
  4. {
  5. mask *.php
  6. mask *.fphp
  7. cmd "/usr/bin/php-cgi -c /php-ini/dir/ -f $script_filename"
  8. }
  9. default_fcgi
  10. {
  11. unix_socket_prefix "/usr/home/site_user/tmp/f"
  12. io_timeout 10000
  13. sleep_timeout 30000
  14. request_processing_timeout 15000
  15. max_processes 5
  16. requests_per_process 1000
  17. }
  18. fcgi
  19. {
  20. path "/usr/home/site_user/www/upload_file.fcgi"
  21. unix_socket_prefix "/usr/home/site_user/tmp/f"
  22. io_timeout 30000
  23. sleep_timeout 30000
  24. request_processing_timeout 3600000
  25. max_processes 25
  26. requests_per_process 1000
  27. }

Как писать PHP-скрипт

FastCGI — это интерфейс для работы со службой, обрабатывающей запросы. Служба замечательна тем, что сохраняет состояние между обработкой запросов, то есть не обрабатывает каждый запрос с чистого листа. Такой подход теоретически позволяет однократно выполнить интерпретацию скрипта и загрузку в память необходимых модулей. Особенность текущей реализации различных интерфейсов (Apache mod_php, CGI, FastCGI) для PHP в том, что код скрипта не меняется под каждый интерфейс, из-за чего сохраняется совместимость и появляется относительная свобода в выборе интерфейса, но исключается возможность использования преимуществ отдельного интерфейса.

  1. <?php
  2. echo "Мой PID: " . getmypid();
  3. ?>

Если несколько раз запустить приведенный скрипт, он напечатает один и тот же PID. В результате за счет отказа от тяжелого web-сервера и от многократного перезапуска скрипта будет получен определенный выигрыш в производительности. Правда основной потенциал интерфейса при этом не задействуется, и связано это с архитектурной кривизной PHP. Практически в любом скрипте есть ресурсы и данные, которые можно использовать многократно для обработки разных запросов: соединение с СУБД, подготовленные к выполнению SQL-запросы, загруженные данные конфигурационных файлов и т.д. В реализации FastCGI-интерфейса для языков Perl, С и других эти ресурсы выносятся за цикл обработки запросов, инициализируются однократно и переиспользуются. В PHP же все эти данные теряются и для обработки следующего запроса нужно опять устанавливать подключение к базе данных, загружать настройки из файлов и т.д.

В сети существуют половинчатые (и в плане архитектуры, и в плане работоспособности) решения этой проблемы, смысл которых — реализация совмещенного со скриптом сервера приложений на PHP. То есть скрипт сам внутри себя организует цикл обработки запросов, как и положено, работая в режиме службы. Недостаток такого подхода — нестандратность решения и необходимость модификации скриптов. Изменяя скрипты, приходится завязываться на чудную, нефункциональную, неоттестированную и редкую реализацию. В случае обнаружения ошибок, например, с ростом нагрузки, придется проводить болезненную процедуру по откату скриптов к классическому виду, либо самому дорабатывать решение напильником.

Хорошим решением тут была бы правильная простая бинарная реализация интерфейса FastCGI, возможно, с внесением изменений в интерпретатор PHP. При должном интересе к этой теме возможно будет дан старт таких исследований. В любом случае переход на FastCGI для PHP — дело стоящее и обещающее в будущем увеличение производительности.