Ускоряем PHP (с ReactPHP)

    В этом посте я хотел бы поделиться не совсем обычным, для мира PHP, способе построения приложения, если угодно — архитектурой. Данный подход позволяет средствами PHP увеличить количество обрабатываемых запросов в разы. Так же я поделюсь своими наработками в этом направлении. Конечно данный подход не бесплатен, в плане требований к коду, но давайте всё по порядку.




    Да, в конце мы получим прирост производительности в 30 раз по сравнению с обычным PHP и в 6 раз по сравнению с PHP + OPcache. Но с начала, хотел бы поговорить о существующих, популярных решениях по улучшению быстродействия PHP приложений.

    OPcache

    Большинство современных иснталяций используют APC/OPcache и это считается стандартом и максимум для PHP. У этого подхода наименьшее количество недостатков, т.к. это нативное (родное) решение предлагаемое нам командой PHP. Всё бы хорошо, но скорости маловато.

    HHVM

    HHVM действительно хорош, для популярных Linux дистров уже есть репозитории и остаётся только поставить и настроить, что в целом дело не хитрое. Но это разработка от команды facebook и на данный момент, HHVM сильно ограничивает в выборе расширений, а если у вас вдруг свои патчи для PHP расширений то и вовсе ставит «крест» на безболезненном переходе с PHP на HHVM. Про PHP 5.5 так же можно забыть. Стоит отметить отличную работу facebook команды по увеличению совместимости HHVM с основными инструментами и фреймворками, но это цифра всё таки в районе 97%.

    Из приходящих мне в голову вариантов остаются еще сырой HippyVM и фреймворк PhalconPHP. О Phalcon написано много обзоров и думаю повторять их нет смысла. HippyVM в стадии разработки, кстати это альтернатива HHVM от самой же facebook команды, написан на python, что на мой взгляд делает этот проект еще более туманным.

    Другие варианты предлагайте в комментариях.

    Классическая инсталляция PHP включает в себя установку одного из Nginx, Apache или Lighttpd веб сервера, которые обрабатывают входящие HTTP запросы и перенаправляют динамические к PHP. Существует несколько вариантов подключения PHP к веб серверу:
    • mod_php (apache)
    • f(ast)cgi
    • php-fpm

    Все решения по ускорению PHP в целом направлены на ускорение медленного интерпретатора PHP в момент перенаправления запроса от веб сервера к скрипту, что, как видно по тестам быстродействия даёт свой результат. Но в данном решении есть недостаток, как ни крути, но на каждый запрос PHP приложению приходится объявлять классы, создавать экземпляры, подключаться к базам, читать кэш — инициализировать своё окружение. И как бы мы не ускоряли интерпретатор PHP, но на всю инициализацию тратится много ресурсов и такой подход явно далёк от желаемого, особенно для высоконагруженных решений. Почему так происходит? PHP был изначально сконструирован как язык шаблонов и набора инструментов, и не задумывался как самостоятельный веб сервер. К тому же в PHP нет параллельного выполнения или даже асинхронного как у node.js, а все написанные расширения блокирующие.

    Но PHP не стоит на месте. У нас появилась своя экосистема с тысячами инструментов которые легко можно установить благодаря Composer. PHP позаимствовал много патернов у таких языков как Java и других, привет команде Symfony & Co. Появились инструменты позволяющие работать PHP асинхронно. На эту тему уже есть статья на хабре, по этому не буду повторятся в описании этого подхода. Скажу только, что асинхронный подход позволяет нам создавать не только чат на websocket, но и запускать полноценный HTTP сервер, а это значит что нам не придётся инициализировать PHP на каждый запрос. Таким образом, не сложно догадаться, что такой подход сведёт на нет затрачиваемое время на старт различных фреймворков и в конечном счёте улучшиться время отклика.

    Данное решение, как понятно из заголовка, построено на ReactPHP. Сам React это скорее инструмент для создания, а не готовое решение. Хотя в нём уже есть инструменты для обработки входящих Http соединений, а так же есть различные инструменты, например для работы с websockets или async redis, но нет реализованных привычных для современных фреймворков MVC патерна, роутинга и т.д. Для этих целей мы подключим ReactPHP к уже существующему Symfony2 приложению.

    ReactPHP основывается на eventloop и для реализации этой архитектуры предлагает на выбор установить одну из ext-libevent, ext-libev, ext-event. В случае отказа, React работает через stream_select и возможности асинхронности сводятся к минимуму, т.к. по сути всё будет выполняться по очереди без возможности на прерывания процесса. Конечно, можно это опустить, т.к. по сути асинхронность, это и есть череда задач в пределах одного процесса. Но если функция будет использовать не блокирующие вызовы, то eventloop базирующийся на stream_select будет вынужден ждать выполнения этой функции, т.к. не возможности прервать функцию на время выполнения не блокирующего вызова, например к async-redis. Конечно это можно обойти разбиением функционала, но суть проблемы ясна.

    Я сторонник нативных решений, и инсталляция pecl расширений туда не очень входит. К тому же установка pecl потребуется на всём парке серверов да и на хостингах будут проблемы. А ведь у PHP есть возможность реализации корутин средствами PHP 5.5. Благодаря замечательной статье от nikic (перевод на хабре), я решил впилить свою реализацию eventloop на основе описанного nikic планировщика задач. Да звучит не просто, и с непривычки действительно требует основательного изменения представления построения приложений на PHP. Но на мой взгляд за такими решениями будущее PHP.

    Кстати Symfony был выбран не случайно. Реализация стека обработки входящих запросов Symfony, нам позволяет с лёгкостью работать не убивая PHP после каждого запроса. А пока я допиливал этот пост, предложения с подобной реализацией уже поступают на канале Symfony. И сами разработчики не скрывают, что подобное решение у них теплится в умах с начала запуска 2 версии.

    Но давайте перейдём от слов к делу. Для начала нам потребуется ваш любимый Linux дистрибутив с установленными и настроенными nginx, php-cli 5.5.x, composer и вашим приложением на Symfony. Если у вас нет под рукой Symfony приложения, то можно взять голую инсталляцию с Symfony сайта, на которой и будет приведён пример. Если вам и composer не знаком, то вкратце можно ознакомится в моей статье к Satis.

    Создаём новую папку, если проект уже есть то заходим в него:
    mkdir fastapp && cd fastapp
    

    Устанавливаем composer:
    curl -sS https://getcomposer.org/installer | php
    

    Ставим Symfony2.4.4:
    php composer.phar create-project symfony/framework-standard-edition symfdir/ 2.4.4 && mv symfdir/* ./ && rm -fr symfdir
    

    Получаем
    ls -l
    
    drwxrwxr-x  6 user user 4.0K Apr 30 11:25 app/
    drwxrwxr-x  2 user user 4.0K Apr 30 11:25 bin/
    drwxrwxr-x  3 user user 4.0K Mar 14 09:37 src/
    drwxrwxr-x 13 user user 4.0K Apr 30 11:25 vendor/
    drwxrwxr-x  3 user user 4.0K Apr 30 11:25 web/
    -rw-rw-r--  1 user user 2.0K Mar 14 09:37 composer.json
    -rw-rw-r--  1 user user  56K Apr 30 11:25 composer.lock
    -rwxr-xr-x  1 user user 990K Apr 30 11:23 composer.phar*
    -rw-rw-r--  1 user user 1.1K Mar 14 09:37 LICENSE
    -rw-rw-r--  1 user user 5.7K Mar 14 09:37 README.md
    -rw-rw-r--  1 user user 1.3K Mar 14 09:37 UPGRADE-2.2.md
    -rw-rw-r--  1 user user 2.0K Mar 14 09:37 UPGRADE-2.3.md
    -rw-rw-r--  1 user user  356 Mar 14 09:37 UPGRADE-2.4.md
    -rw-rw-r--  1 user user 8.3K Mar 14 09:37 UPGRADE.md
    


    Добавляем такие строчки в ваш composer.json:
    {
        "repositories": [
            { "type": "vcs", "url": "http://github.com/Imunhatep/rephp" },
            { "type": "vcs", "url": "http://github.com/Imunhatep/php-pm" }
        ],
        "minimum-stability": "dev",
        "prefer-stable": true,
        "require": {
            "imunhatep/php-pm": "@dev"
        }
    }
    

    Чтоб выглядело примерно так
    {
        "name": "symfony/framework-standard-edition",
        "license": "MIT",
        "type": "project",
        "description": "The \"Symfony Standard Edition\" distribution",
        "autoload": {
            "psr-0": { "": "src/" }
        },
        "repositories": [
            { "type": "vcs", "url": "http://github.com/Imunhatep/rephp" },
            { "type": "vcs", "url": "http://github.com/Imunhatep/php-pm" }
        ],
        "minimum-stability": "dev",
        "prefer-stable": true,
        "require": {
            "php": ">=5.3.3",
            "symfony/symfony": "~2.4",
            "doctrine/orm": "~2.2,>=2.2.3",
            "doctrine/doctrine-bundle": "~1.2",
            "twig/extensions": "~1.0",
            "symfony/assetic-bundle": "~2.3",
            "symfony/swiftmailer-bundle": "~2.3",
            "symfony/monolog-bundle": "~2.4",
            "sensio/distribution-bundle": "~2.3",
            "sensio/framework-extra-bundle": "~3.0",
            "sensio/generator-bundle": "~2.3",
            "incenteev/composer-parameter-handler": "~2.0",
            "imunhatep/php-pm": "@dev"
        },
        "scripts": {
            "post-install-cmd": [
                "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters",
                "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap",
                "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
                "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
                "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile"
            ],
            "post-update-cmd": [
                "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters",
                "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap",
                "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
                "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
                "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile"
            ]
        },
        "config": {
            "bin-dir": "bin"
        },
        "extra": {
            "symfony-app-dir": "app",
            "symfony-web-dir": "web",
            "incenteev-parameters": {
                "file": "app/config/parameters.yml"
            },
            "branch-alias": {
                "dev-master": "2.4-dev"
            }
        }
    }
    


    Запускаем обновление пакетов:
    php composer.phar update
    

    Получаем
    Loading composer repositories with package information
    Updating dependencies (including require-dev)         
      - Installing stack/builder (v1.0.1)
        Loading from cache
    
      - Installing react/promise (v2.0.0)
        Loading from cache
    
      - Installing guzzle/parser (v3.9.0)
        Loading from cache
    
      - Installing evenement/evenement (v2.0.0)
        Loading from cache
    
      - Installing react/react (v0.4.1)
        Loading from cache
    
      - Installing imunhatep/rephp (dev-master 13adf26)
        Cloning 13adf2697681a5954978ac56fe2c8fdf6a21dc4a
    
      - Installing imunhatep/php-pm (dev-master 02f44ec)
        Cloning 02f44ecb41ca5b4c81d4bb6087da7a0ed4964656
    
    
    react/react suggests installing ext-libevent (Allows for use of a more performant event-loop implementation.)
    react/react suggests installing ext-libev (Allows for use of a more performant event-loop implementation.)
    react/react suggests installing ext-event (Allows for use of a more performant event-loop implementation.)
    Writing lock file
    Generating autoload files
    Updating the "app/config/parameters.yml" file
    Clearing the cache for the dev environment with debug true
    Installing assets using the hard copy option
    Installing assets for Symfony\Bundle\FrameworkBundle into web/bundles/framework
    Installing assets for Acme\DemoBundle into web/bundles/acmedemo
    Installing assets for Sensio\Bundle\DistributionBundle into web/bundles/sensiodistribution
    


    Подготавливаем Symfony cache:
    php app/console cache:warmup --env=dev
    

    И запускаем веб сервер, пока средствами только PHP и в одном экземпляре, тестовый так сказать. Порт можно подобрать по вкусу:
    php bin/ppm start --workers 1 --port 8080
    

    Проверяем, что всё работает открыв в любимом браузере localhost:8080. Должна открыться страничка приветствия от Symfony, правда картинки не покажутся и css неподгрузится. Таким образом мы получили PHP веб сервер, обрабатывающий и входящие запросы и не умирающий. Но у нас только 1 процесс, нет обработки статики и нет балансера. Как многие догадались, для этого нам и понадобится nginx.


    Настраиваем nginx для проксирования динамических запросов на наш PHP сервер, попутно выполняя роль балансера, а статику отдавать без участия PHP:
    upstream backend  {
        server 127.0.0.1:5501;
        server 127.0.0.1:5502;
        server 127.0.0.1:5503;
        server 127.0.0.1:5504;
        server 127.0.0.1:5505;
        server 127.0.0.1:5506;
        server 127.0.0.1:5507;
        server 127.0.0.1:5508;
    }
    
    server {
        root /path/to/symfony/web/;
        server_name fastapp.com;
    
        location / {
                    # try to serve file directly, fallback to rewrite
                    try_files $uri @rewriteapp;
            }
    
            location @rewriteapp {
                    if (!-f $request_filename) {
                            proxy_pass http://backend;
                            break;
                    }
            }
    }
    

    При этом server_name (fastapp.com) нужно прописать в /etc/hosts:
    127.0.0.1   fastapp.com
    

    Теперь чтоб нам не мучиться с ручным запуском n-количества процессов нашего PHP приложения (представленная nginx конф. настроена на n=8), заходим в папку нашего проекта и выполняем:
    cp vendor/imunhatep/rephp/ppm.json ./
    

    Подправляем ./ppm.json файлик:
    {
            "bootstrap": "\\PHPPM\\Bootstraps\\Symfony",
            "bridge": "HttpKernel",
            "appenv": "dev",
            "workers": 8,
            "port": 5501
    }
    

    Иногда после изменений требуется обновить кэш, возможно это только в моём случае, т.к. при написании статьи производил изменения в коде:
    app/console cache:warmup --env=dev
    

    Заново запускаем наш PHP Process Manager:
    php bin/ppm start
    

    Получаем в ответ:
    8 slaves (5501, 5502, 5503, 5504, 5505, 5506, 5507, 5508) up and ready.
    

    Сначала проверяем в браузере линк localhost:5501, если всё открылось то пробуем открыть fastapp.com. Должно всё открываться, с картинками и css.

    Теперь можно жечь при помощи тулзы siege или ab, на выбор:
    siege -qb -t 30S -c 128 http://fastapp.com/
    

    Приведу несколько результатов тестирования своего (не helloworld) Symfony приложения, на девелоперской машине с AMD 8core, 8RAM и Fedora20.
    Php 5.5.10, через nginx + php-fpm:

    siege -qb -t 30S -c 128 http://login.dev/signup
    
    Lifting the server siege...      done.
    
    Transactions:                 983 hits
    Availability:              100.00 %
    Elapsed time:               29.03 secs
    Data transferred:            4.57 MB
    Response time:                0.91 secs
    Transaction rate:           34.26 trans/sec
    Throughput:                0.16 MB/sec
    Concurrency:               124.23
    Successful transactions:         983
    Failed transactions:               0
    Longest transaction:            1.81
    Shortest transaction:            0.42
    

    Php 5.5.10 с включенным OPcache, через nginx + php-fpm:

    siege -qb -t 30S -c 128 http://login.dev/signup
    Lifting the server siege...      done.
    
    Transactions:                5298 hits
    Availability:              100.00 %
    Elapsed time:               29.54 secs
    Data transferred:           24.15 MB
    Response time:                0.70 secs
    Transaction rate:          179.35 trans/sec
    Throughput:                0.82 MB/sec
    Concurrency:              126.43
    Successful transactions:        5298
    Failed transactions:               0
    Longest transaction:            1.68
    Shortest transaction:            0.07
    

    Php 5.5.10 с включенным OPcache, через nginx + ReactPHP + Coroutine eventloop:

    siege -qb -t 30S -c 128 http://fastlogin.dev/signup
    Lifting the server siege...      done.
    
    Transactions:               30553 hits
    Availability:              100.00 %
    Elapsed time:               29.85 secs
    Data transferred:          157.63 MB
    Response time:                0.12 secs
    Transaction rate:         1023.55 trans/sec
    Throughput:                5.28 MB/sec
    Concurrency:              127.43
    Successful transactions:       30553
    Failed transactions:               0
    Longest transaction:            0.76
    Shortest transaction:            0.00
    

    Увеличиваем количество параллельных запросов до 256.
    Php 5.5.10 с включенным OPcache, через nginx + php-fpm

    siege -qb -t 30S -c 256 http://login.dev/signup
    
    siege aborted due to excessive socket failure;
    
    Transactions:                 134 hits
    Availability:               10.48 %
    Elapsed time:                1.58 secs
    Data transferred:            0.78 MB
    Response time:                1.21 secs
    Transaction rate:           84.81 trans/sec
    Throughput:                0.49 MB/sec
    Concurrency:              102.93
    Successful transactions:         134
    Failed transactions:            1145
    Longest transaction:            1.56
    Shortest transaction:            0.00
    

    К сожалению php-fpm свалился и отказался работать с лимитом в 32 процесса против 256 параллельных запросов.
    Пробуем Php5.5.10 + ReactPHP + Coroutine eventloop

    siege -qb -t 30S -c 256 http://fastlogin.dev/signup
    
    Lifting the server siege...      done.
    
    Transactions:               29154 hits
    Availability:              100.00 %
    Elapsed time:               29.16 secs
    Data transferred:          150.40 MB
    Response time:                0.25 secs
    Transaction rate:          999.79 trans/sec
    Throughput:                5.16 MB/sec
    Concurrency:              252.70
    Successful transactions:       29154
    Failed transactions:               0
    Longest transaction:            3.66
    Shortest transaction:            0.00
    

    Заключение.


    Идея запускать Symfony приложения через ReactPHP не моя, позаимствовал у Marc из его статьи, за что ему большое спасибо. Кстати он делал свои замеры и даже сравнивал с HHVM. Ниже приведён график из его статьи:

    Мой вклад заключается в создании eventloop на основе работы nikic и допиливании менеджера процессов до, в целом, работоспособности, а также нюансов запуска ReactPHP с новым eventloop. Возможно с pecl event lib, будет это всё работать быстрее, не проверял. К сожалению мои текущие проекты не соответствуют требуемому качеству кода, вот наработка и пылится на полках «лаборатории», т.к. такой подход требует кода без ошибок. То есть PHP, по сути, не имеет права падать, а всеядность и динамика PHP ни как этому не способствует. Это можно исправить, дописав PHP PM, чтоб перезапускал упавшие процессы, а так же можно дописать отслеживание изменений в коде и также перезапускать процессы. Но пока не востребовано. Так же на этой базе можно запускать и websocket сервер. Что было в планах, но так там и осталось.

    Оставлял такой сервер на все выходные под нагрузкой, утечек памяти не было. Есть одна проблема которую пока нет ни времени ни необходимости искать: по каким то причинам, после нагрузки, остаются не закрытыми 1-2 соединения. На малых нагрузка выявить причину не удаётся, а для больших нужно потратить время чтоб придумать как выявить причину. Пока что, добавил таймер, которые каждые 10 секунд проверяет текущие соединения на валидность (ресурс, не ресурс) и убивает мёртвые.

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

    Подробнее
    Реклама
    Комментарии 64
    • +1
      И самый интересный вопрос — возможно ли ReactPHP скрестить в битриксом в качестве пускателя?
      • +3
        Скрестить с ReactPHP будет не просто. Природа обычных запросов к PHP скриптам в том, что они умирают. А с ReactPHP предполагается что скрипт будет выполняться продолжительное время. Более того, в рамках одного запуска будут обрабатываться разные соединения разных пользователей. Это сильно меняет подход к работе. Скрестить если и можно — это будет совсем не тривиальная задача. По хорошему проект нужно разрабатывать с изначальной мыслю о том, что он будет работать с ReactPHP.
        • 0
          А есть ли фреймворки, кроме Symfony, выбирающие принцип действия в зависимости от типа сервера? К примеру, если сервер использует классическую конфигурацию, то и работать по классическому принципу, если же веб-приложение запущено как приложение, то и повести себя соответствующим образом — единовременно выполнить инициализацию, затем в ответ на каждый запрос вызывать соответствующий ему callback? Причём чтобы это было прозрачно, то есть, index.php можно было бы и обслуживать общепринятым способом, и запускать как приложение?
          • 0
            Отмечу, что Symfony работает по единому принципу вне зависимости от сервера, грубо говоря, запускает новый экземпляр приложения на каждый запрос.

            К сожалению ответа на ваш вопрос я не знаю, но интерсно былоб послушать от знающих.
            • +1
              Фреймворк должен лишь предоставить абстракцию над запросами/ответами, остальное только в ваших руках. Скажем любой фреймворк базирующийся на HttpKernel (Laravel например или Silex) можно заставить выполнять инициализацию лишь раз и потом просто форвардить запросы/ответы (то есть приложение инициализируется в рамках процесса-воркера). Скажем с Yii этого сделать не выйдет, ибо там ключевые компоненты завязаны на суперглобальных массивах, и подменить их будет проблемно. Как обстоят дела с Yii2 я не в курсе.

              В целом подход с одиночной инициализацией несет в себе много подводных камней, так что сходу ответить на этот вопрос сложно. В частности если у нас есть сессии и прочее, то задача несколько усложняется.
              • 0
                А сколько примерно времени занимает инициализация HttpKernel по сравнению со всем остальным. Имеет ли смысл внедрять это?
                • 0
                  Время на создание объекта Request + инициализация класса имплентящего HttpKernelInterface (в контексте ReactPHP можно пренебречь, так как он уже будет инициализирован) + вызов метода handle + формирование объекта Response (допустим просто создание инстанса). И у нас полностью готова цепочка запрос-ответ, есть возможность создавать декораторы приложений (для реализации http middleware) и возможность покрывать все функциональными тестами без использования штук типа selenium/phantom.js/zombi.js ну и просто очень качественная абстракция наз запросами/ответами и собственно их обработкой, что является основным плюсом в концепции приложений на ReactPHP, ибо у нас нету $_GET/$_POST массивов и т.д.

                  Словом, этот компонент больше для структуры приложения, оверхэда он не несет.
        • +1
          У меня есть очень тонкое подозрение, что на ReactPHP правильнее строить сайт с нуля, я не запускать существующий, тем более такую «полностью вещь в себе», как Битрикс. )

          Да и зачем оно Вам? Битрикс — продукт, параметры которого известны. Вы не сделаете сайт быстрее чем сколько-то мс на страницу, но задержки, при грамотно сделанном сайте, никого не удавят. Тем более что свежепредставленное кеширование дает иллюзию мгновенности отдачи страницы, но, опять же, требует грамотного разработчика. Который, кстати, должен знать, какие задачи на Битриксе хорошо делаются, а какие в него «не ложатся».

          Берите php-fpm + nginx, и разводите на них (без апача), перенеся ЧПУ (точнее, маршрутизацию) в конфигу nginx. Но прежде всего, посмотрите, от чего тормоза — не в логике ли сайта дело, ведь тогда Вам никакие ускорители не помогут.
        • 0
          Обратил внимание, что PM использует pcntl, может с pthreads было бы лучше?
          • 0
            Если честно, то я не силён в преимуществах разных методов распараллеливания php. Но phthreads, это pecl расширение, а pcntl работает из коробки.

            P.S. Если есть свой сервер и не надо заботиться о совместимости Php, то можно много хорошего добавить в Php, мне вот очень понравилось расширение типизации для примитвных типов в Php, от того же nikic.
            • 0
              Ну как бы, распараллеливание потоками и процессами имеет свои преимущества. Потоки проще, проще организовать общение между ними, меньше расходов на старт потока, меньше расходов на память (в теории). Процессы надежнее, но сложнее. Если у вас упадет воркер, он не потащит за собой весь сервер.
              • 0
                То есть в случае с тредами, при ошибке в одном из потоков, падает весь процесс… Думаю, в случае с php, это не самый лучший подход. Или же придётся использовать сторонние решения для управления сервером, как supervisor. Но тогда теряется смысл использования процесс менеджера на php (php-pm), а хотелось бы, чтоб инструменты были написанны на том же языке.
                • 0
                  php-fpm как по мне явно лишнее звено в стэке. Как я это вижу:

                  ReactPHP запущен через cli и занимается обработкой запросов. При получении запроса он отдает в поток/процесс воркер его на обработку и продолжает ловить новые запросы. Воркер получает request и должен отдать response обратно.

                  Причем для ускорения всего этого нужно еще реализовать механизм prefetch-а, периодически перезапуская потоки/процессы воркеры. Если у вас хватит сил сделать что-то вроде middleware, то у вас будет решение для запуска любого приложения на базе HttpKernel через ReactPHP.

                  На самом деле ваши бенчмарки не имеют никакой практической пользы, ибо вы испытываете helloworld приложение. Интереснее было бы увидеть разницу с hhvm на примере реального приложения. И еще было бы неплохо добавить в бенчмарки статистику по потреблению памяти.
                  • +1
                    • php-fpm != php-pm, как раз в изложеном подходе мы избавилсь от php-fpm
                    • зачем нужно переодичеки перезапускать, если всё работает?
                    • если я правильно понимаю вашу идею с prefetch, то она уже реализована в OPcache
                    • бенчмарки, как мои так и Marc делались не на helloworld, о чём я указал перед тестами. Marc тестил Kryn.cms, а я взял страницу с регистрацией из реального продукта
                    • По поводу памяти, я упомянул, что утечек я не увидел. Памяти будет использовано столько же сколько и обычно… Тестируемое приложение, на рабочих серверах требует ~40мб APC памяти, похожее потребление я видел и при нагрузках на ReactPHP процессах
                    • 0
                      Извините, как-то не внимательно прочел.

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

                      По поводу памяти, c hhvm потребление памяти в разы ниже.

                  • 0
                    Не падает весь процесс.

                    gist.github.com/hiend/53c2a8e554957a85746f
                    • –1
                      Хмм интересно. Я б сказал даже странно. Мне кажется это былоб логичным, если кинутый Exception в потоке, «всплывал» в главном процессе, и в случае если не отлавливается, то процесс должен умирать с «Uncaught exception».
                      • 0
                        «всплывал» в главном процессе

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

                        По поводу термина «главный». Вобщем то он просто первый, ничем не главнее других. Все равноправно могут вызвать exit процесса, все могут не вызывать. Кстати, если обойти стандартный механизм завершения программы (с которым приедться столкнуться при попытке завершить «первый» поток), то процесс может легко существовать без «первого» потока. Важно наличие хотябы одного потока, любого.
                      • 0
                        Любопытно. Спасибо.
              • 0
                Правильно ли я понимаю, что такой подход потребует иных подходов в разработке собственно приложений? Придется следить за памятью, коннектами, деструкторами, в общем вещами слегка непривычными для нынешних типовых проектов?
                • +1
                  Да, следует помнить, что приложение целиком загружено в память и не перегружается для каждого запроса. Не стоит будучи в prod грузить в память гигабайтные файлы или на всё время жизни блокировать ресурсы)
                  • 0
                    squаre всё верно подметил, я лишь добавлю, что это также даёт преимущество, в том, что многое теперь можно не писать в кэш, ведь php не умирает. Но нужно чётко разделять ресурсы принадлежащие к данному запросу (сессия, данные пользователя) и ресурсы для процесса в целом (базаданных, очереди, кеш).
                    • 0
                      Раз все загружено в память, то есть ли какой-то подход обновлять код без передергивания?
                      app/console cache:warmup --env=dev
                      php bin/ppm start
                      


                      • 0
                        Основываясь на собственном опыте сомневаюсь, что такое возможно.
                        Только если реализовать некоторый специальный контролер, который будет перечитывать некоторые фрагменты, типа конфигураций.
                        • 0
                          Docker. Оберните все это в контейнер, неспешно поднимите новый, потушите старый и даунтайм будет минимален.
                          • –1
                            Docker — это хорошо. Но, к сожалению, всё ещё не для production.
                            • +1
                              13 февраля 2016

                              Вы серьезно? Даже docker swarm в продакшен годится уже (версия 1.0 довольно стабильна). С версии 1.9 у докера наконец отпала необходимость в кастылях в виде link и data-only контейнеров (а уже версия 1.10). Docker-compose все еще бесполезен по сути, но в версии 1.7-1.8 мои bash скрипты смогут чуточку похудеть.

                              Переубеждать не буду, но могу сказать, что более чем годится он для продакшена, либо жду от вас истории неудач.
                          • 0
                            В целом возможно, разные варианты реализации. Обсуждаем тут github.com/marcj/php-pm/issues/14
                      • +4
                        «И так» — это примерно как «ться» или «жы-шы», только еще круче.
                        • +4
                          Спасибо, но всё же уместней в личку.
                        • +1
                          В чем преимущества и фундаментальные отличия от phpDaemon (http://daemon.io/)?
                          • +2
                            С любителями goto нам не по пути. Это вам не linux core :)

                            Не верите? Вотъ github.com/kakserpom/phpdaemon/blob/master/PHPDaemon/Clients/HTTP/Connection.php#L211
                            • 0
                              вообще-то у goto есть несколько оправданных применений, и одно из них — конечные автоматы. в данном случае это конечный автомат, и применение goto может быть допустимо

                              (правда, в этом конкретном случае автомат очень простой, и вполне можно было обойтись без goto)
                              • 0
                                Вы, кажется, не понимаете в чём проблема goto. В PHP конструкция без этой проблемы.
                                • 0
                                  Я понимаю, отчего же, проблема всего одна — злой раптор ;)
                              • +2
                                Пожалуй вопросу можно посвятить отдельную статью…

                                Ну то что мне сразу бросилось в глаза: phpDaemon представляет из себя готовое, монолитное решение — нет модульности. Подружить с другими фремворками будет не просто. К тому же он всё так же требует pecl расширений. Субъективно, подход в React основан на более современных подходах к разработке асинхронных решений — EventMachine (Ruby), Twisted (Python) and Node.js (V8).

                                На самом деле я бы подсмотрел решение phpDaemon в области запуска дочерних процессов, т.к. там уже впилены мониторинг и состояния процессов и изменения в исходниках, плюс перегрузка при необходимости.
                              • +3
                                Это очень правильный подход к разработке веб сервисов. Умирать после каждого запроса — это пережиток прошлого, наследние PHP так сказать.
                                Другое дело, что разрабатывать такие приложения необходимо с нуля, с учётом этих особенностей, нельзя просто так взять и перевести существующий сайт на этот подход (хотел сказать «на эти рельсы», но причём здесь ruby?).
                                Возникает другой вопрос — а почему бы не начать разработку на языке/фреймворке, который изначально ориентируется на данных подход? Не холивора ради, но сложность переделки сайта под данный подход на PHP сравнима с переходом на другой язык.
                                • 0
                                  В идеальном мире, да, нужно писать проект изначално с мыслью о таком подходе, но запустить можно уже существующий Symfony2 и получить выйгрыш в скорости. Переходить на другой язык можно самому и если есть время. А команде с готовыми решениями это близко к нереалному. Думаю пример facebook это наглядо показывает.
                                  • +1
                                    В случае нормальных фреймворков и/или архитектуры приложения изменения должны быть минимальны. Главное, по сути, чтобы были отделены инициализация самого приложения и обработка запроса. И изменения нужно вносить только в инфраструктурный код, а не переписывать всю бизнес-логику, логику хранения и т. п.
                                  • 0
                                    Все прекрасно и красиво, утечек нет, в пхп работает GC, но все забывают один маленький нюанс — пхп не деаллокатит занятую память. Если вдруг ваш сервак «пиканёт» на секундочку заняв пару десятков гигабайт памяти, то он их не отпустит уже никогда (в рамках процесса, конечно же). Иногда это становится проблемой весьма ощутимой…

                                    Это я к чему — если вы вдруг решитесь на проект с такой архитектурой — вспомните мое предостережение, чтобы не плакать, поедая этот кактус.
                                    • 0
                                      Спасибо за заметку.
                                      Есть обходной путь: использовать воркеры. Костыль конечно, но все же. Либо через менеджер очередей, либо классический вариант выполнения php через php-fpm.
                                      Например, второй путь я использовал недавно в проекте (в статье была ссылка) для загрузки и обработки картинок. В принципе, обычный подход к реализации: клиентская часть обращается к некоему загрузочному скрипту через AJAX для загрузки, происходит обработка и запись результатов виде файлов и поля в БД, а после успеха на работающий демон посылается команда для обновления кеша модели.
                                      • 0
                                        А можно где-то подробнее прочитать об этом? Сейчас поставил небольшой эксперимент, и память деаллоцируется, судя по всему.
                                        • +1
                                          Опровергну сам себя:
                                          провел простейшие тесты на версиях php-5.2.17-Win32-VC6-x86, php-5.3.28-nts-Win32-VC9-x86, php-5.5.12-nts-Win32-VC11-x86 и выяснил, что память освобождается успешно и довольно быстро. Возможно мой неудачный опыт был связан с каким нибудь внешним расширением, которые совсем необязателно так-же лояльны к памяти, ну или особенностями операционных систем, на которых происходил мой кейс с пиком…

                                          <?php
                                          $array = range(1,9999999);
                                          $link = &$array;
                                          $link = null;
                                          
                                          while (1) {
                                              //endless loop to check process memory usage after unset
                                          }
                                          
                                        • 0
                                          Поддержу Fesor. Конечно специально не проверял, но насколько помню, когда проводил тесты, то потребляемая память при нагрузках повышалась до 60-70мб, а через какое то время возвращалась к 40мб.
                                      • 0
                                        А проблему работы скрипта с базами данных (SQL) аккуратно пропустили в статье. Даже если все повесить на persistent connect это почти не решит проблемы накладных расходов. После чего вся скорость и преимущества ReactPHP и других решений меркнет перед жуткими мыслями, как переписать код чтобы он вообще начал работать. Панацей по сути могут быть только кеши, аля nginx+memcache, где РНР используется только тогда, когда надо закинуть данные в кеш. И даже если страницы динамичные, то 80% страницы статика и только небольшая часть ее меняется, что тоже решается не сложными плясками вокруг кеша.
                                        • +2
                                          Поясните, пожалуйста, что вы имеете в виду? Синхронную природу функций работы с БД? Но есть ведь и асинхронные.
                                          Так в чем сложность, можно иллюстрацию на каком-нибудь примере?
                                          • 0
                                            Ну начнем с того, что про работу с функциями типа LAST_INSERT_ID() можно забыть. Транзакции, лок таблиц, использование переменных сессии и т.д. Асинхронные запросы это конечно хорошо, но это не решает проблемы выделения отдельных конектов к БД для их работы, что влечет за собой дальше использование пулов и работы с ними. Я все же убежден что лучше хороший кеш, чем быстрая БД (SQL), если мы говорим про выдачу данных, а не их обработку. И еще добавлю, не знаю как ReactPHP, но всякое использование постоянно загруженного приложения в РНР вело к проблемам с памятью, если не брать в учет примеры типа «Hello World». Возможно в последних версиях этого стало меньше, но последний раз когда я сталкивался с подобными задачами в РНР 5.3 с этим были большие проблемы, очень большую роль играет, то какая архитектура приложения.
                                            • 0
                                              Оверхед на подключение к БД не страшен, тем более что можно использовать persistent connection. Ну да, желательно организовать пул, ничего страшного, но да, несколько отличается от привычной простоты.
                                              Точно так же где требуется — надо ставить локи, только конечно не таблиц, а записей, не myisam одним благо живы. Где критично — там конечно же memcache или redis.Тут я не вижу принципиального отличия от классической схемы использования php через воркеры.
                                              Что до памяти, то у меня на php 5.5 в ReactPHP точно не течёт, периодически мониторю. Другие люди тоже не жаловались. В каких-то сценариях такое наверное возможно.
                                              • 0
                                                Я же мысль развивал, не для того что бы сказать, что это не рабочий инструмент. А хотел объяснить, что часто и густо на рабочих проектах оверхеды сводят на нет подобные реализации. Сам ReactPHP не течет, но он же и не рабочее приложение, а только обертка по сути. Я в свое время убивал уйму времени на подобные способы ускорения работы РНР. Потом, заморочился и сделал связку Mongoose+РНР, первые тесты тоже были очень красивые, но когда включил рабочее приложение все производительность исчезла и осталось порядка 10% прироста, что для меня было не приемлемо.

                                                Для Casus, без пулов вы очень быстро уложите любую БД при нормальной нагрузке. Нельзя на один конект к БД повесить все что у вас есть, максимум что вы сможете выделить в этом варианте — это одно ядро (или поток), много ли оно вам даст производительности? Пул для того и нужен чтобы эффективно нагружать машину и в зависимости от ее тех. характеристик — количества ядер и памяти. И то что «Сессия, в Symfony, привязана к запросу» никак не поможет с тем что я перечислял для асинхронного режима.

                                                Для nikita2206, такой подход оправдан если делаем только одни SELECTты, но тогда следом вопрос, а зачем их вообще делать если можно тупо кинуть в кеш? Если будем использовать INSERT + SELECT уже нарвемся на дикие очереди на процессоре. Нельзя вот так просто дать все на откуп быстрым воркерам. Я в своей практике работал с проектом, где к БД было порядком 70-80 тыс запросов в минуту (днем), и могу сказать, что приложение работало банально под связкой mod_php (apache) и вся проблема была только в распределении нагрузки на БД.

                                                П.С.: Я не предвзят, просто опыт работы с решением подобных проблем налаживает свой отпечаток.
                                              • +2
                                                Поясните, зачем вам пул? Если хотите чтоб на каждый запрос было отдельное соединение, ну так можно создавать подключение на каждый запрос и закрывать на завершение запроса, благо есть евенты. Но на мой взгляд и одного соединения на процесс достаточно. Для last_insert_id() или транзакций нужно просто не разбивать последовательное выполнение блока. Вы же сами решаете когда отдать управление планировщику.

                                                Сессия, в Symfony, привязана к запросу и нет проблем с разделением сессионных данный для разных запросов в одном процессе.
                                                Проблемы с памятью нужно отлавливать и устранять, как и в других языках. Если вы не используете малопопулярные расширения, то скорее всего проблема в вашем приложении.
                                                • 0
                                                  Т.к. ответа нет на коммент Casus, напишу тоже, — в чем вообще проблема? Если это воркер, который живет постоянно, то он может и коннект держать постоянно, это наоборот как бы плюс React-ового подхода.
                                              • 0
                                                Ну штуки типа varnish (а не какие-то странные решения типа nginx+memcache) стоит ставить между приложением и nginx, благо благодаря HttpKernel это относительно легко организовать.
                                                • 0
                                                  А в чем странность решения nginx+memcache? Такая связка очень хорошо себя оправдывает для простой статики, для картинок так и подавно. Ну а про varnish, это как бы и не кеш в широком понятии, т.к. «it is a reverse http proxy that caches». Пока мне хватало связки haproxy ->nginx->memcache. Можно конечно воткнуть varnish после haproxy, но это несет дополнительные расходы на железо.
                                                  • 0
                                                    Да ничего особо странного, просто интересно насколько это профитнее. Обычно достаточно только встроенных механизмов кеширования на уровне файловой системы, или же складывать кэш nginx-а прямо в память. Это если мы говорим о статике. Интересно сравнить производительность при разных подходах именно на отдачу статики.

                                                    Если же memcached используется как хранилище динамического контента (сгенерили страничку — положили в кэш), то тут все понятно.
                                              • 0
                                                ReactPHP работал без APC/OPCache?
                                                • 0
                                                  А вы не читали статью?
                                                  • 0
                                                    Из статьи это непонятно.
                                                    • 0
                                                      У каждого результата есть заголовок. Там написано «с включенным OPcache»
                                                      • 0
                                                        Извиняюсь, ночью было дело.
                                                        Тогда удивлён, почему Php 5.5.10 с включенным OPcache, через nginx + ReactPHP + Coroutine eventloop показало 30 тысяч запросов, а Php5.5.10 + ReactPHP + Coroutine eventloop без OPcache почти столько же, 29 тысяч.

                                                        Ещё хотелось бы понять, почему на графике nginx+react даже для 5 потоков настолько быстрее чем просто react. Казалось бы ведь идёт простое проксирование запросов, и результат не должен отличаться. Я понимаю разницу на большом количестве потоков, где nginx играет роль балансера, и пускает одновременно запускаться только 8 потоков из 256, снижая таким образом нагрузку на процессор.
                                                        • 0
                                                          ну на самом деле если скрипт все время висит в памяти и только в одном экземпляре эффект от opcache незначительный.

                                                          В статье, откуда был взят график, nginx проксирует запросы на 6 инстансов reactphp. Как раз таки nginx тут играет роль балансера.
                                                          • 0
                                                            Потоков 6, но ядер 8, то есть бутолочного горлышка нет. Почему же тогда nginx+hhvm не показывают такого значительного прироста?
                                                            • 0
                                                              Ядер в том серваке было как раз таки 6. Если честно — не могу ответить, надо разбираться, но мне тоже любопытно.

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