ReactPHP это сокет сервер на PHP созданный для постоянной обработки запросов в отличии от стандартного подхода с Apache и Nginx где процесс умирает по окончании обработки одного запроса. Поскольку инициализация кода таким образом осуществляется только один раз то на отдельном запросе мы упускаем весь оверхед от загрузки классов, запуска фреймворка, считывания конфигурации итд.
Ограничением тут является то, что программист должен помнить что процесс и все поднятые сервисы будут использованы множество раз и по этому доступ к глобальному или статическому скоупу не желателен. Это делает сложным использование ReactPHP с большинством фреймворков не созданных для такого подхода.
К счастью PHPixie сама отказалась от глобального и статического скоупов, что позволяет легко запустить ее из-под ReactPHP.
Для начала добавим его поддержку в проект:
php ~/composer.phar require react/react
Затем создаем файл react.php в корневой папке:
<?php
require_once('vendor/autoload.php');
$host = 'localhost';
$port = 1337;
$framework = new Project\Framework();
$framework->registerDebugHandlers(false, false);
$app = function ($request, $response) use ($framework, $host, $port) {
$http = $framework->builder()->components()->http();
// Строим реальную URI запроса
$uri = 'http://'.$host.':'.$port.$request->getPath();
$uri = $http->messages()->uri($uri);
// Приводим запрос к PSR-7
$serverRequest = $http->messages()->serverRequest(
$request->getHttpVersion(),
$request->getHeaders(),
'',
$request->getMethod(),
$uri,
array(),
$request->getQuery(),
array(),
array(),
array()
);
// Передаем запрос в фреймворк
$frameworkResponse = $framework
->processHttpServerRequest($serverRequest);
// Вывод ответа
$response->writeHead(
$frameworkResponse->getStatusCode(),
$frameworkResponse->getHeaders()
);
$response->end(
$frameworkResponse->getBody()
);
};
$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$http = new React\Http\Server($socket);
$http->on('request', $app);
$socket->listen($port);
$loop->run();
Запускаем:
php react.php
Теперь перейдя по ссылке localhost:1337/ видим ту же PHPixie только запущенную как сервер. Простой бенчмарк на дефолтном контроллере показал увеличение производительности примерно в 8 раз, что не удивительно если взять во внимание то на сколько меньше кода выполняется при каждом запросе. Для тех кто захочет повторить мой эксперимент заметьте что я добился наилучшего результата с библиотекой event как бекенда для ReactPHP (он может работать и без нее, но только чуть медленнее получается).
Правда сам ReactPHP вносит несколько ограничений. Во-первых вам все равно понадобится веб-сервер для статических файлов. Но самое грустное то, что из коробки он не поддерживает данные из тела запроса ($_POST), хотя существуют способы добиться этого.
Наличие постоянного рантайма открывает интересные возможности, как например создание чата без надобности во внешней базе данных. Конечно пока-что это только эксперимент, но если идея приживется то PHPixie может получить отдельный компонент с более широкой поддержкой ReactPHP, включая например сессии и загрузку файлов.
Комментарии (42)
WST
14.08.2015 09:07+1Можно было бы использовать новую фишку uWSGI — phpsgi, вот только автор пока, похоже, отложил её в долгий ящик — не реализована как минимум обработка данных в POST-запросах.
Кстати, встроенные возможности uWSGI (планировщик, спулер, мулы и т.д.) могли бы решить часть проблем с блокирующими операциями.Fesor
14.08.2015 11:03+1обработка данных в POST запросах реализована, не реализована конкретно обработка multipart запросов, это несколько другая штука и в принципе ее можно добавить, готовые парсеры запросов существуют, единственнео что я под это дело все же отдельный бы воркер запилил.
vlreshet
14.08.2015 10:43+2Работает быстрее зато данные из $_POST нужно тащить какими-то костылями, и всячески избегать синхронных операций и утечек памяти. ИМХО, так себе улучшение. Не более чем «поиграться и забыть». Не представляю себе это в продакшине.
jigpuzzled
14.08.2015 11:07Ну доя например рид-онли сайтов вполне может подойти думаю. Конечно в любой проект засунуть не получится
SamDark
14.08.2015 11:23+1Рид-онли можно и статику нагенерить и раздавать тем же nginx…
jigpuzzled
14.08.2015 12:17Ну зависимо от количества. Я уже видел статический магазин на 8 гигабайт файлов как следствие множества продуктов и страниц. Для таких имхо статиуа уже проблемно
Fesor
14.08.2015 11:11+1данные из $_POST нужно тащить какими-то костылями
не кастылями, а через абстракцию над SAPI (тот же PSR-7 предоставляет чудные интерфейсы для этого дела).
и всячески избегать синхронных операций и утечек памяти
Ну с утечками памяти да, хотя опять же можно просто иногда перезагружать воркеры. Что до «избегать сихронных операций» — я чуть ниже отписал.
SamDark
14.08.2015 10:48Буквально вчера замерял то же на Yii 2.0 и пришёл к выводу о практической бесполезности использования этого дела в рамках фреймворка…
jigpuzzled
14.08.2015 11:11Поигрался еще и все действительно упирается в синхронний доступ к БД. Сейчас пробую php-pm
Fesor
14.08.2015 11:07+4Как уже отмечали люди выше, писать асинхронщину бывает больно, но если есть желание сделать что-то типа честного fastcgi и при этом не менять код то есть альтернативное решение на базе того же reactphp — php-pm. По сути это менеджер процессов который будет поднимать ваше приложение и ожидать запроса. После завершения обработки запроса можно либо перезагрузить воркер либо просто почистить его (например в случае с доктриной — отчистить UoW). Если вся наша система является stateless или легко сама себя чистит нам более не придется перезагружать воркеры и тратить серверное время на бутстрап приложения с инициализацией сервисов и т.д. Что до утилизации CPU — можно просто увеличить количество воркеров или сделать нормальный пайплайнинг — например отдельный коркер принимающий запросы и отдельные воркеры для их разбора.
В самом простом случае можно просто поднять RPS за счет устранения времени на бутстрап приложения (все же обычно не изветсно оставляют ли сервисы что-то после себя и ускорение за счет того что приложение будет висеть всегда уже нужно будет намного серьезнее тестить на всякие побочные эффекты).
jrip
14.08.2015 12:10Типа «пишем демона на PHP»? Ну ок, а зачем?
Производительность? Вы с чем конкретно сравнивали?
>в отличии от стандартного подхода с Apache и Nginx где процесс умирает по окончании обработки одного запроса
вроде как такие стандартные подходы остались в веселом прошлом?jigpuzzled
14.08.2015 12:18Не остались ) фреймворки поднимаются с нуля при каждом запросе
jrip
14.08.2015 13:16А почему вы не любите чтото вроде apc? По сравнению с ним, я думаю не будет такого ускорения.
Fesor
14.08.2015 14:42+1apc (к слову нынче opcache, который еще с 5.5 по умолчанию в ядре пыха) это лишь опкод кэшер, он не избавляет нас от необходимости на каждый запрос инициализировать систему, заного подключаться к базе и т.д. Он всего-лишь избавляет от необходимости работы с файловой системой так как уже распаршенные опкоды висят в общей памяти. Opcache еще в довесок расширяет оптимизации дополнительно, например кэш строк хранит не для процесса а для всего пула процессов, чем экономит память существенно… Даже в этом случае инициализация приложения это какое-то время, которое для некоторых критично (скажем если у вас 100 милисекунд на запрос это порог, вы явно захотите убрать лишние 10-15 милисекунд инициализации системы.
jrip
14.08.2015 16:35В заголовке написано — ускоряет в 8 раз. Вот интересно было по сравнению с чем, с голым апачем и php как cgi? Ну так толку так сравнивать. Apc или что-то другое это уже тонкости.
Fesor
14.08.2015 16:41Автор проверял hello world, пустой контроллер, никакой бизнес логики и т.д. То есть по сути измерения проводились между «разбор запроса + маршрутизация + время инициализация приложения» и «разбор запроса + маршрутизация без учета времени инициализации приложения».
То что автор не описал методику испытаний это уже само по себе попахивает маркетингом… но по сути без разницы с чем сравнивать, цифры будут примерно одинаковыми. Как никак если пых у нас был без opcache в одном случае то во втором это просто не играет роли так как весь код и так в памяти крутится.
jrip
14.08.2015 16:54>но по сути без разницы с чем сравнивать
В том то и прикол, что если запустить олдскульно PHP как CGI на Апаче то разница возможно как раз и будет гдето в десять раз.
Ну вот я и удивился — оно так нафига делать)Fesor
14.08.2015 17:05оно так нафига делать
Давайте будем реалистами, скорее всего автор взял свой типовой стэк и на нем все гонял. Откуда взялась цифра «в 8 раз» я в принципе уже расписал — это ускорение за счет отсутствия времени инициализации фреймворка. Профит более чем очевидный, более того php-pm уже довольно часто используется для увеличения RPS на продакшене.
Fesor
14.08.2015 12:27+2вроде как такие стандартные подходы остались в веселом прошлом?
Нет, после завершения запроса процесс просто перезапускается и в случае с php-fpm нет расходов по времени на запуск самого PHP. А вот приложение как запускалось с нуля так и запускается на каждый запрос (разве что с opcache это дело сильно ускоряется). Подходы с php-pm позволяют нивелировать и эти накладные расходы и при этом продолжать писать сихронный код не заботясь о том что у нас там демоны.jrip
14.08.2015 13:14+1> (разве что с opcache это дело сильно ускоряется)
Я всякое подобное и имею ввиду, а есть еще apc, hhvm и тд и тп.
Вот лично я в чистом виде apache + php давно в серьезных проектах не видел.Fesor
14.08.2015 14:46в чистом виде apache + php давно в серьезных проектах не видел.
Ну потому и нет смысла об этом упоминать.
а есть еще apc, hhvm и тд и тп.
apc уже нет (только для php5.4 и менее), есть opcache + apcu разве что, hhvm это совсем другая штука, там ускорение опять же за счет того что у нас:
— код хранится в памяти и его не надо разбирать заного (мы этого можем добиться включив opcache и вырубив полностью инвалидацию кэша, хоть это имеет смысл делать только на больших нагрузках)
— JIT позволяет оптимизировать опкоды и генерить оптимизированный под текущую задачу машинный код. В PHP на данный момент для каждого опкода в каждый момент времени выполняется один и тот же машинный код (что логично), а HHVM в зависимости от контекста — разный, за счет этого мы получаем возможность оптимизаций. PHP7 подготовил неплохую почву для внедрения JIT в будущем, да и в рамках OpCache чуваки из Zend уже выложили их PoC реализацию (правда которая пока не особо работает и ждет своего часа).jrip
14.08.2015 16:37>Ну потому и нет смысла об этом упоминать
Так я с чего и начал, с чем сравнивают то? А так то писать демона на PHP на нагрузках это не сильно здравая идея.evnuh
14.08.2015 21:45+2As we can see, the react server with nginx as load balancer is over 15 times faster than old school PHP-FPM + APC.
> marcjschmidt.de/blog/2014/02/08/php-high-performance.html
VolCh
16.08.2015 23:49Это сильно зависит от задачи. Зачастую куда дешевле демонизировать существующий код, чем переписывать его на другом языке, тем более если надо будет поддерживать синхронно обе версии.
VolCh
16.08.2015 12:23Как зачем? Чтобы увеличить производительность без смены используемого стакан технологий.
slonopotamus
14.08.2015 21:26-2Давно не следил за PHP, в нем уже перестала течь память и его больше не нужно перезапускать каждые N обработанных запросов?
Fesor
14.08.2015 21:41+3с версии 5.3 сборщик мусора начал поддерживать циклические ссылки, так что проблему могут составлять только кривые расширения. В целом же уже добрых лет 5 можно писать демоны на PHP.
tyderh
16.08.2015 17:06-3Проблема в том, что php создан, чтобы умирать. Так что более интересны долговременные эффекты использования этой штуки.
Fesor
16.08.2015 19:51А если кто-то напишет статью в духе «php теперь может не умирать» вы ему тоже поверите наслово? У меня в продакшене крутится уже почти пол года апишка на reactphp (по сути архитектура такая же как и в php-pm но с минимальными отличиями), знаю людей у которых php-pm крутится, и так же никаких проблем.
Проблемы с долгоиграющими PHP скриптами обычно вызваны:
— руки из одного места
— кривая обработка ошибок
— использование стремных экстеншенов для PHP (стандартные штуки которые нужны для всего этого добра, типа libev/libeveb, libuv, pcntl проблем не вызывали) которые текут.
В целом же проблем с php-pm нет вообще никаких, у нас в качестве демона только менеджер процессов и обработка запросов, а воркеры вы можете хоть после каждого запроса перезапускать.qRoC
17.08.2015 09:23-1Не пробовали привязать jit? Должна получится интересная связка.
SamDark
17.08.2015 18:13А при чём тут JIT?
qRoC
17.08.2015 18:14Я про связку reactphp + jit
SamDark
17.08.2015 18:37+1JIT для PHP — это неудавшийся в плане прироста производительности эксперимент. master PHP7 даст больше. Посмотрите выступление Дмитрия Стогова с devconf.
Fesor
17.08.2015 18:50ну не совсем неудавшийся, так как это вынудило создать phpng, какой-никакой а профит. То что валяется сейчас на гитхабах в теории должно ускорять всякие мелочи типа вызовов функций и т.д. просто до phpng накладные расходы на управление памятью были слишком большие (работа с кучей, большие кэш мисы и т.д), а после phpng уже просто не занимались JIT-ом, времени небыло. В перспектике JIT в совокупности с phpng даст еще больший профит, и кто знает, может кто-то возьмется пока чуваки из zend заняты.
StirolXXX
Проблема в том, что любая блокирующая операция (запрос в базу данных, file_get_contents итд.) заблокирует всех клиентов.
Потому писать код в таком ключе нужно совсем по другому…
nazarpc
И да, и не совсем.
В обычном PHP-FPM у нас же тоже всё блокируется. Как решаем проблему? Создаем пул воркеров и распределяем нагрузку по ним. Вот только в случае с ReactPHP не приходится поднимать всю систему заново, классы уже загружены, некоторые неизменные объекты тоже можно использовать для разных запросов, именно это и дает основной прирост производительности — PHP перестает умирать после каждого запроса.
KAndy
что мешает запустить n серверов одновременно и балансировать нагрузку с nginx