image
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)


  1. StirolXXX
    14.08.2015 08:50
    +3

    Проблема в том, что любая блокирующая операция (запрос в базу данных, file_get_contents итд.) заблокирует всех клиентов.

    Потому писать код в таком ключе нужно совсем по другому…


    1. nazarpc
      14.08.2015 15:11
      +1

      И да, и не совсем.
      В обычном PHP-FPM у нас же тоже всё блокируется. Как решаем проблему? Создаем пул воркеров и распределяем нагрузку по ним. Вот только в случае с ReactPHP не приходится поднимать всю систему заново, классы уже загружены, некоторые неизменные объекты тоже можно использовать для разных запросов, именно это и дает основной прирост производительности — PHP перестает умирать после каждого запроса.


    1. KAndy
      14.08.2015 21:12

      что мешает запустить n серверов одновременно и балансировать нагрузку с nginx


  1. WST
    14.08.2015 09:07
    +1

    Можно было бы использовать новую фишку uWSGI — phpsgi, вот только автор пока, похоже, отложил её в долгий ящик — не реализована как минимум обработка данных в POST-запросах.
    Кстати, встроенные возможности uWSGI (планировщик, спулер, мулы и т.д.) могли бы решить часть проблем с блокирующими операциями.


    1. Fesor
      14.08.2015 11:03
      +1

      обработка данных в POST запросах реализована, не реализована конкретно обработка multipart запросов, это несколько другая штука и в принципе ее можно добавить, готовые парсеры запросов существуют, единственнео что я под это дело все же отдельный бы воркер запилил.


  1. vlreshet
    14.08.2015 10:43
    +2

    Работает быстрее зато данные из $_POST нужно тащить какими-то костылями, и всячески избегать синхронных операций и утечек памяти. ИМХО, так себе улучшение. Не более чем «поиграться и забыть». Не представляю себе это в продакшине.


    1. jigpuzzled
      14.08.2015 11:07

      Ну доя например рид-онли сайтов вполне может подойти думаю. Конечно в любой проект засунуть не получится


      1. SamDark
        14.08.2015 11:23
        +1

        Рид-онли можно и статику нагенерить и раздавать тем же nginx…


        1. jigpuzzled
          14.08.2015 12:17

          Ну зависимо от количества. Я уже видел статический магазин на 8 гигабайт файлов как следствие множества продуктов и страниц. Для таких имхо статиуа уже проблемно


          1. Fesor
            14.08.2015 12:25
            +1

            Почему проблемно? Varnish и вперед.


    1. Fesor
      14.08.2015 11:11
      +1

      данные из $_POST нужно тащить какими-то костылями

      не кастылями, а через абстракцию над SAPI (тот же PSR-7 предоставляет чудные интерфейсы для этого дела).

      и всячески избегать синхронных операций и утечек памяти

      Ну с утечками памяти да, хотя опять же можно просто иногда перезагружать воркеры. Что до «избегать сихронных операций» — я чуть ниже отписал.


  1. SamDark
    14.08.2015 10:48

    Буквально вчера замерял то же на Yii 2.0 и пришёл к выводу о практической бесполезности использования этого дела в рамках фреймворка…


    1. jigpuzzled
      14.08.2015 11:11

      Случайно отослал коммент два раза


    1. jigpuzzled
      14.08.2015 11:11

      Поигрался еще и все действительно упирается в синхронний доступ к БД. Сейчас пробую php-pm


      1. gibson_dev
        14.08.2015 13:13
        +1

        Можно и асинхронный попробовать, в mysqli есть такая возможность


  1. Fesor
    14.08.2015 11:07
    +4

    Как уже отмечали люди выше, писать асинхронщину бывает больно, но если есть желание сделать что-то типа честного fastcgi и при этом не менять код то есть альтернативное решение на базе того же reactphp — php-pm. По сути это менеджер процессов который будет поднимать ваше приложение и ожидать запроса. После завершения обработки запроса можно либо перезагрузить воркер либо просто почистить его (например в случае с доктриной — отчистить UoW). Если вся наша система является stateless или легко сама себя чистит нам более не придется перезагружать воркеры и тратить серверное время на бутстрап приложения с инициализацией сервисов и т.д. Что до утилизации CPU — можно просто увеличить количество воркеров или сделать нормальный пайплайнинг — например отдельный коркер принимающий запросы и отдельные воркеры для их разбора.

    В самом простом случае можно просто поднять RPS за счет устранения времени на бутстрап приложения (все же обычно не изветсно оставляют ли сервисы что-то после себя и ускорение за счет того что приложение будет висеть всегда уже нужно будет намного серьезнее тестить на всякие побочные эффекты).


  1. jrip
    14.08.2015 12:10

    Типа «пишем демона на PHP»? Ну ок, а зачем?
    Производительность? Вы с чем конкретно сравнивали?
    >в отличии от стандартного подхода с Apache и Nginx где процесс умирает по окончании обработки одного запроса
    вроде как такие стандартные подходы остались в веселом прошлом?


    1. jigpuzzled
      14.08.2015 12:18

      Не остались ) фреймворки поднимаются с нуля при каждом запросе


      1. jrip
        14.08.2015 13:16

        А почему вы не любите чтото вроде apc? По сравнению с ним, я думаю не будет такого ускорения.


        1. Fesor
          14.08.2015 14:42
          +1

          apc (к слову нынче opcache, который еще с 5.5 по умолчанию в ядре пыха) это лишь опкод кэшер, он не избавляет нас от необходимости на каждый запрос инициализировать систему, заного подключаться к базе и т.д. Он всего-лишь избавляет от необходимости работы с файловой системой так как уже распаршенные опкоды висят в общей памяти. Opcache еще в довесок расширяет оптимизации дополнительно, например кэш строк хранит не для процесса а для всего пула процессов, чем экономит память существенно… Даже в этом случае инициализация приложения это какое-то время, которое для некоторых критично (скажем если у вас 100 милисекунд на запрос это порог, вы явно захотите убрать лишние 10-15 милисекунд инициализации системы.


          1. jrip
            14.08.2015 16:35

            В заголовке написано — ускоряет в 8 раз. Вот интересно было по сравнению с чем, с голым апачем и php как cgi? Ну так толку так сравнивать. Apc или что-то другое это уже тонкости.


            1. Fesor
              14.08.2015 16:41

              Автор проверял hello world, пустой контроллер, никакой бизнес логики и т.д. То есть по сути измерения проводились между «разбор запроса + маршрутизация + время инициализация приложения» и «разбор запроса + маршрутизация без учета времени инициализации приложения».

              То что автор не описал методику испытаний это уже само по себе попахивает маркетингом… но по сути без разницы с чем сравнивать, цифры будут примерно одинаковыми. Как никак если пых у нас был без opcache в одном случае то во втором это просто не играет роли так как весь код и так в памяти крутится.


              1. jrip
                14.08.2015 16:54

                >но по сути без разницы с чем сравнивать
                В том то и прикол, что если запустить олдскульно PHP как CGI на Апаче то разница возможно как раз и будет гдето в десять раз.
                Ну вот я и удивился — оно так нафига делать)


                1. Fesor
                  14.08.2015 17:05

                  оно так нафига делать

                  Давайте будем реалистами, скорее всего автор взял свой типовой стэк и на нем все гонял. Откуда взялась цифра «в 8 раз» я в принципе уже расписал — это ускорение за счет отсутствия времени инициализации фреймворка. Профит более чем очевидный, более того php-pm уже довольно часто используется для увеличения RPS на продакшене.


    1. Fesor
      14.08.2015 12:27
      +2

      вроде как такие стандартные подходы остались в веселом прошлом?

      Нет, после завершения запроса процесс просто перезапускается и в случае с php-fpm нет расходов по времени на запуск самого PHP. А вот приложение как запускалось с нуля так и запускается на каждый запрос (разве что с opcache это дело сильно ускоряется). Подходы с php-pm позволяют нивелировать и эти накладные расходы и при этом продолжать писать сихронный код не заботясь о том что у нас там демоны.


      1. jrip
        14.08.2015 13:14
        +1

        > (разве что с opcache это дело сильно ускоряется)
        Я всякое подобное и имею ввиду, а есть еще apc, hhvm и тд и тп.
        Вот лично я в чистом виде apache + php давно в серьезных проектах не видел.


        1. 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 реализацию (правда которая пока не особо работает и ждет своего часа).


          1. jrip
            14.08.2015 16:37

            >Ну потому и нет смысла об этом упоминать

            Так я с чего и начал, с чем сравнивают то? А так то писать демона на PHP на нагрузках это не сильно здравая идея.


            1. evnuh
              14.08.2015 21:45
              +2

              As 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


            1. VolCh
              16.08.2015 23:49

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


    1. VolCh
      16.08.2015 12:23

      Как зачем? Чтобы увеличить производительность без смены используемого стакан технологий.


  1. slonopotamus
    14.08.2015 21:26
    -2

    Давно не следил за PHP, в нем уже перестала течь память и его больше не нужно перезапускать каждые N обработанных запросов?


    1. Fesor
      14.08.2015 21:41
      +3

      с версии 5.3 сборщик мусора начал поддерживать циклические ссылки, так что проблему могут составлять только кривые расширения. В целом же уже добрых лет 5 можно писать демоны на PHP.


  1. tyderh
    16.08.2015 17:06
    -3

    Проблема в том, что php создан, чтобы умирать. Так что более интересны долговременные эффекты использования этой штуки.


    1. Fesor
      16.08.2015 19:51

      А если кто-то напишет статью в духе «php теперь может не умирать» вы ему тоже поверите наслово? У меня в продакшене крутится уже почти пол года апишка на reactphp (по сути архитектура такая же как и в php-pm но с минимальными отличиями), знаю людей у которых php-pm крутится, и так же никаких проблем.

      Проблемы с долгоиграющими PHP скриптами обычно вызваны:
      — руки из одного места
      — кривая обработка ошибок
      — использование стремных экстеншенов для PHP (стандартные штуки которые нужны для всего этого добра, типа libev/libeveb, libuv, pcntl проблем не вызывали) которые текут.

      В целом же проблем с php-pm нет вообще никаких, у нас в качестве демона только менеджер процессов и обработка запросов, а воркеры вы можете хоть после каждого запроса перезапускать.


      1. qRoC
        17.08.2015 09:23
        -1

        Не пробовали привязать jit? Должна получится интересная связка.


        1. SamDark
          17.08.2015 18:13

          А при чём тут JIT?


          1. qRoC
            17.08.2015 18:14

            Я про связку reactphp + jit


            1. SamDark
              17.08.2015 18:37
              +1

              JIT для PHP — это неудавшийся в плане прироста производительности эксперимент. master PHP7 даст больше. Посмотрите выступление Дмитрия Стогова с devconf.


              1. Fesor
                17.08.2015 18:50

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


                1. qRoC
                  17.08.2015 19:57

                  Вот я про это и говорю. С данным подходом становится допустимо потратить один раз некоторое время(на самом деле довольно большое) на выполнение дополнительных оптимизаций.


                  1. Fesor
                    17.08.2015 21:22

                    какие дополнительные оптимизации?