Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

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

Anna | 29.05.2014 | нет комментариев
В этом посте я хотел бы поделиться не вовсе обыкновенным, для мира 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.

Кстати Syмfony был выбран не нечаянно. Реализация стека обработки входящих запросов 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 SymfonyBundleFrameworkBundle into web/bundles/framework
Installing assets for AcmeDemoBundle into web/bundles/acmedemo
Installing assets for SensioBundleDistributionBundle 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), а не выполнятся монолитно. Так же отлично бы применять не блокирующий функционал.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB