8 февраля 2015 в 19:44

WSGI/Rack для PHP

Исторически сложилось, что скрипты на PHP запускаются при каждом HTTP-запросе. Запускаясь, скрипт проводит какую-то инициализацию (например, устанавливает соединение с СУБД), после чего анализирует запрос и формирует ответ. Однако, всем прекрасно известно, что в мире Python и Ruby принят другой подход: веб-приложения на этих языках загружаются в память единовременно вместе с веб-сервером (или сервером приложений). Взаимодействие сервера приложений со скриптом осуществляется при помощи стандартных интерфейсов WSGI и Rack. Такой подход, безусловно, не лишён недостатков, главный из которых, пожалуй, связан с резким ростом накладных расходов при размещении большого числа сайтов на одном сервере, однако, обладает и важным преимуществом: инициализация производится лишь однократно, затем скрипт лишь отвечает на входящие HTTP-запросы.

Попытки перенести цикл обработки запросов в тело PHP-сценария уже производились, при этом удавалось добиться значительного роста производительности. Однако, для этого приходилось писать достаточно много кода.

Однако, буквально на днях создатели uWSGI реализовали экспериментальную поддержку интерфейса, схожего с WSGI/Rack, для PHP. Я напомню, что uWSGI — чрезвычайно гибкий и функциональный сервер приложений, поддерживающий практически все существующие на сегодняшний день языки и технологии, который легко может быть использован как альтернатива PHP-FPM. Таким образом, появилась возможность создания веб-приложений, постоянно загруженных в память, куда меньшей кровью.

Интерфейс назван «phpsgi». Плагин, реализующий поддержку этого интерфейса, ещё пока достаточно сыроват, однако, разработчик уже изъявил желание показать его широкой общественности.

But i will absolutely ask you for tests/reports and for gathering people willing to try it :)


Установка плагина при наличии уже настроенного uWSGI предельно проста:

uwsgi --build-plugin https://github.com/unbit/uwsgi-phpsgi


После выполнения такой команды в текущем каталоге появится файл «phpsgi_plugin.so», который достаточно поместить в каталог с остальными плагинами сервера (обычно /usr/lib/uwsgi). После этого можно настроить вассала (виртуальный узел), либо запустить экземпляр uWSGI вручную. Для простоты рассмотрим второй вариант.

uwsgi --plugin phpsgi --http-socket :9090


Если выполнить такую команду, uWSGI обратится к файлу с именем «app.php», который должен содержать в себе функцию «application». На данный момент, так как плагин является экспериментальным, эти имена жёстко определены в коде. Рассмотрим простейший пример кода на PHP, который можно «скормить» этому плагину.

<?php
echo 'Loading';
function application($env) {
    return ['200 OK', ['Content-Type' => 'text/plain'], 'Hello, world!'];
}


Этот пример выведет в браузер строку «Hello, world!», в то время как строка «Loading» будет выведена однократно в журнал вассала uWSGI.
Илья @WST
карма
47,0
рейтинг 0,1
Инженер-программист
Самое читаемое Разработка

Комментарии (25)

  • –15
    Как раз с другом на днях пришли к выводу о том, что PHP морально устарел, сравнивая современные web-фреймворки. А ключевой в споре оказалась именно возможность проинициализовать всё необходимое во время запуска (скомпилировать статический контент, провести миграции БД, загрузить сторонние библиотеки в память).
    • 0
      Решаемо написанием адаптера, позволяющего использовать любимый фреймворк с таким интерфейсом (если, конечно, не возникнет трудностей из-за архитектуры самого фреймворка).
  • +1
    Что будет с сервером, если в скрипте обнаружится fatal error?
    • 0
      Полагаю, что вассал поругается в лог (если директива display_errors включена) и прекратит нормальное функционирование до тех пор, пока не будет перезапущен по какой-либо причине (например, по достижении max-requests), после чего снова отвалится, когда произойдёт новая ошибка. Если же ошибка происходит при запуске скрипта, uWSGI сообщает, что «no app is loaded».

      Для сервера в целом это опасности не представляет — ни для соседних вассалов, ни для императора. Прекращает нормальное функционирование лишь сам вассал (но ведь при фатальных ошибках сайт прекращает работать и при классическом способе запуска, с той лишь разницей, что если ошибка проявляется лишь на одной странице, это не обрушивает остальные). Впрочем, утверждать, что в плагине нет уязвимостей, я не могу.
      • 0
        >прекратит нормальное функционирование до тех пор, пока не будет перезапущен по какой-либо причине (например, по достижении max-requests)
        Значит ли это, что пока не наберется max-requests, вассал будет возвращать ошибку на любые запросы?
        • 0
          Да, именно это и значит. К сожалению, не могу вам сходу ответить, есть ли какой-нибудь способ перезапускать сбойные воркеры (кстати, забыл уточнить — вассал может включать в себя несколько воркеров, и в такой ситуации может получиться так, что один отвалится, а остальные останутся работать), по идее, конечно, такой способ должен быть. Точно есть способ динамического определения требуемого числа воркеров в зависимости от нагрузки, но это, конечно, не то. С другой стороны, так ли часто случаются именно фатальные ошибки? Чаще всего если они происходят, то это говорит о наличии серьёзной проблемы в коде.

          Вы можете задать свой вопрос разработчику через багтрекер — они без внимания вопросы пользователей обычно не оставляют.
          • 0
            >так ли часто случаются именно фатальные ошибки?
            все мы люди и склонны ошибаться :)

            даже если мы ошибаемся редко — это не отменяетнеприятных последствий
  • +4
    А как в современных версиях PHP с управлением памятью? Возможно ли эффективное длительное выполнение скрипта?
    • +1
      На данном этапе разработки сильно течёт сам плагин. Это написано в его описании. Что же касается самого PHP, есть основания ему доверять, так как достаточно людей писали на PHP различные консольные приложения, например, я писал бота для XMPP, он мог работать неделями без существенного роста значений VSZ/RSS. Кроме того, всегда есть опция max-requests, позволяющая подобрать компромиссное значение числа выполненных запросов.
    • +2
      php сильно зависит от расширений и для длительного выполнения нужно аккуратно подходить к их выбору

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

      imagick, например, также сильно зависит от библиотек и недавно мы поимели неприятную проблему с апгрейдом ImageMagick из-за старой версии lcms
    • +4
      демоны на php — вполне себе реальность уже несколько лет как. Однако все упирается в то, что мало кто может писать без утечек памяти и с обработкой ошибок, поскольку «по классике» на это проще положить болт.

      Комментарий thecoder-а ниже тому примером.

      При этом этим грешат не только разработчики прикладных приложний, но и разработчики всевозможных библиотек — они даже не пытаются сделать реконнект при обрыве связи по таймауну при работе с внешними сервисами — базами ли, почтовыми ли серверами… почти все затачиваются на выполнение скрипта в пределах нескольких секунд, не более.
  • 0
    PHP создан чтобы умирать. Если работает в качестве сервера — память вроде бы есть, но утечки могут непредсказуемо проявиться практически в любом модуле и после пары тысяч запросов памяти уже нет. Падение, перезапуск. Как на минном поле. Лично я считаю, что это плохой подход для PHP.

    Недавно писал чисто для пробы сервер для HTML5 Websockets, обрабатывать события и кидать сообщения клиентам в браузер. Здесь обработка запросов в цикле без вариантов — надо долго держать соединение. Пустой сервер с тестовыми заглушками работает шустро. После обрастания возможностями, когда начинает лезть в MySQL, Redis — память уходит на глазах. Приходится отслеживать и перезапускать. Все опять сводится к тому, чтобы периодически умирать.
    • +4
      «PHP создан чтобы умирать» — кто-то просто обязан был повторить эту знаменитую фразу в таком топике :)

      Я позволю себе не согласиться. Такое заявление слишком категорично, оно ставит крест на самой идее развития языка. Гораздо правильнее другой подход — есть утечка памяти, значит, есть бага, а баги нужно исправлять. Я думаю, вы ведь не будете отрицать, что ситуация с управлением памятью PHP за годы существования PHP 5.x значительно улучшилась?
      По поводу вашей задачи — здесь дело может быть не только в утечках, но и в разного рода буферах в тех же динамических расширениях, кроме того, работа с постоянно подключёнными клиентами подразумевает непредсказуемость их поведения — к примеру, если у них медленное подключение, могут проявиться симптомы атаки медленным чтением (slow read attack).
      • 0
        К сожалению написал, потому что со многим можно смириться, но из за неприспособленности к длительной работе иногда руки опускаются. Не проблема обработать 10 тысяч запросов отдельно запускаемыми скриптами, но когда нужна обработка запросов в цикле (для сокетов), деваться некуда и перезапуск неизбежен. Т.е. я тупо осознаю, что есть некоторое фиксированное количество запросов, которое сожрет память. Через час, через сутки. Даже если все клиенты быстрые, белые и пушистые. Надеюсь, что настанет прекрасное время, когда движок перепишут и утечки если не пропадут, то не будут бросаться в глаза. Прогресс есть. Медленным шагом, робким зигзагом :) По факту остается мириться, только бы частота перезапусков была в рамках приличия.
        • +3
          неприспособленности к длительной работе

          Вы про PHP или разработчика? Мне как-то кажется что больше второе.
          • 0
            Верно подметили. :) К длительной работе компьютеры больше приспособлены. В контексте темы, чтобы написать демона на php c минимальными утечками, нужно использовать все свое и тратить массу дополнительного времени на отладку. Нет доверия ни к расширениям ни к библиотекам. На данный момент, с учетом требований поддержки и надежности, проще демона на С++ написать, используя реализацию на php в качестве прототипа.
            • +3
              проще демона на С++ написать

              Ну да, ну да… проще… и явно меньше мест где можно допустить ошибку и получить мемори лики или сегментейшен фелт.
  • 0
    Вот тут-то огромное количество каркасов и полетят к черту (кстати, а как там в Symphony дела обстоят), так как у доброй половины объекты запроса хранятся в каком-нибудь DI и вообще сплошняком хранятся в глобальных переменных и вот тут то и станет ясно кто и где с#$л…

    Впрочем есть и хорошая сторона данного вопроса опять возникнет спрос на специалистов, а не на заучивших документацию скрипт-киди школьников.

    Оффтопик: Убежден, что лучше PHP для Frontend языка пока нет.
    • –2
      >> у доброй половины объекты запроса хранятся в каком-нибудь DI

      Так это плюс. Пишем какой-нибудь метод flush() у контейнера и чистим весь DI в конце хита. Еще для сервисов типа коннекта к БД, которые не должны умирать, можно в DI заготовить отдельное местечко. По аналогии с shared (один инстанс сервиса на весь запрос) можно завести какой-нибудь permanent (1 инстанс сервиса на весь воркер).

      >> Убежден, что лучше PHP для Frontend языка пока нет

      А разве PHP не слишком тормозной для фронтенда?
      • +1
        > Пишем какой-нибудь метод flush() у контейнера и чистим весь DI в конце

        Так Вы своим flush() не спасетесь, так как нужно будет обеспечить на момент выполнения запроса безопасность по потокам (thread-safety) или как другой вариант выстраивать все запросы в очередь и тут самая главная «собака» производительности и зарыта. Преимущества подхода «sgi» сразу сводят на нет таким подходом ИМХО…

        > А разве PHP не слишком тормозной для фронтенда?

        А кто не тормозной? С чем сравнивать? Пробежаться по XML с PI (PHP) или пробежаться по XML с Jinja2 (Python) или пробежаться по XML с JSP (Java)? По моему класс задачи равный или я ошибаюсь?

        А если речь идет про всякие HAML, так тут и подавно нет разницы конструирования дерева (чего производительность PRINT сравнивать)?

        Вообщем опять таки мое мнение, что время разработки просто сильно экономиться относительно какой-то Java, Python, Ruby или Perl.

  • 0
    Вот такая штука еще была github.com/jimeh/php-rack
    • 0
      Вот вам в копилку еще один подобный на базе реактора reactphp.org/
  • 0
    Простите, а почему виртуальный узел uWSGI называется «вассал»? Это что-то сильно специфичное? Напомнило http://habrahabr.ru/post/224325/
    • 0
      Там вообще достаточно любопытная терминология — там есть император, император может работать в режиме тирана, императоры могут составлять легион и так далее. Там даже мулы (mules) есть. Моё объяснение такой терминологии таково: там достаточно многоуровневая архитектура, поэтому классический пример мастер-воркер там не совсем уместен. Кстати, понятие мастера там тоже есть — им называется главный процесс вассала. Т.е вассал состоит из мастера и воркеров. Воркеры обслуживают запросы, мастер — проверяет их состояние, перезапускает по различным условиям, также в адресном пространстве мастера выполняются многие подзадачи — запланированные задания, отправка alarm-сообщений через XMPP… Короче, терминология кажется необычной, но вполне оправдана. :)
  • +1
    Стесняюсь спросить, а быстрее то стало? Или тут вопрос правильности? :)

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