Pull to refresh

Comments 64

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

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

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

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

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

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

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

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

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

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

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

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

Хмм интересно. Я б сказал даже странно. Мне кажется это былоб логичным, если кинутый Exception в потоке, «всплывал» в главном процессе, и в случае если не отлавливается, то процесс должен умирать с «Uncaught exception».
«всплывал» в главном процессе

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

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


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

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

Переубеждать не буду, но могу сказать, что более чем годится он для продакшена, либо жду от вас истории неудач.
«И так» — это примерно как «ться» или «жы-шы», только еще круче.
Спасибо, но всё же уместней в личку.
В чем преимущества и фундаментальные отличия от phpDaemon (http://daemon.io/)?
вообще-то у goto есть несколько оправданных применений, и одно из них — конечные автоматы. в данном случае это конечный автомат, и применение goto может быть допустимо

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

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

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

Это я к чему — если вы вдруг решитесь на проект с такой архитектурой — вспомните мое предостережение, чтобы не плакать, поедая этот кактус.
Спасибо за заметку.
Есть обходной путь: использовать воркеры. Костыль конечно, но все же. Либо через менеджер очередей, либо классический вариант выполнения php через php-fpm.
Например, второй путь я использовал недавно в проекте (в статье была ссылка) для загрузки и обработки картинок. В принципе, обычный подход к реализации: клиентская часть обращается к некоему загрузочному скрипту через AJAX для загрузки, происходит обработка и запись результатов виде файлов и поля в БД, а после успеха на работающий демон посылается команда для обновления кеша модели.
А можно где-то подробнее прочитать об этом? Сейчас поставил небольшой эксперимент, и память деаллоцируется, судя по всему.
Опровергну сам себя:
провел простейшие тесты на версиях 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
}
Поддержу Fesor. Конечно специально не проверял, но насколько помню, когда проводил тесты, то потребляемая память при нагрузках повышалась до 60-70мб, а через какое то время возвращалась к 40мб.
А проблему работы скрипта с базами данных (SQL) аккуратно пропустили в статье. Даже если все повесить на persistent connect это почти не решит проблемы накладных расходов. После чего вся скорость и преимущества ReactPHP и других решений меркнет перед жуткими мыслями, как переписать код чтобы он вообще начал работать. Панацей по сути могут быть только кеши, аля nginx+memcache, где РНР используется только тогда, когда надо закинуть данные в кеш. И даже если страницы динамичные, то 80% страницы статика и только небольшая часть ее меняется, что тоже решается не сложными плясками вокруг кеша.
Поясните, пожалуйста, что вы имеете в виду? Синхронную природу функций работы с БД? Но есть ведь и асинхронные.
Так в чем сложность, можно иллюстрацию на каком-нибудь примере?
Ну начнем с того, что про работу с функциями типа LAST_INSERT_ID() можно забыть. Транзакции, лок таблиц, использование переменных сессии и т.д. Асинхронные запросы это конечно хорошо, но это не решает проблемы выделения отдельных конектов к БД для их работы, что влечет за собой дальше использование пулов и работы с ними. Я все же убежден что лучше хороший кеш, чем быстрая БД (SQL), если мы говорим про выдачу данных, а не их обработку. И еще добавлю, не знаю как ReactPHP, но всякое использование постоянно загруженного приложения в РНР вело к проблемам с памятью, если не брать в учет примеры типа «Hello World». Возможно в последних версиях этого стало меньше, но последний раз когда я сталкивался с подобными задачами в РНР 5.3 с этим были большие проблемы, очень большую роль играет, то какая архитектура приложения.
Оверхед на подключение к БД не страшен, тем более что можно использовать persistent connection. Ну да, желательно организовать пул, ничего страшного, но да, несколько отличается от привычной простоты.
Точно так же где требуется — надо ставить локи, только конечно не таблиц, а записей, не myisam одним благо живы. Где критично — там конечно же memcache или redis.Тут я не вижу принципиального отличия от классической схемы использования php через воркеры.
Что до памяти, то у меня на php 5.5 в ReactPHP точно не течёт, периодически мониторю. Другие люди тоже не жаловались. В каких-то сценариях такое наверное возможно.
Я же мысль развивал, не для того что бы сказать, что это не рабочий инструмент. А хотел объяснить, что часто и густо на рабочих проектах оверхеды сводят на нет подобные реализации. Сам ReactPHP не течет, но он же и не рабочее приложение, а только обертка по сути. Я в свое время убивал уйму времени на подобные способы ускорения работы РНР. Потом, заморочился и сделал связку Mongoose+РНР, первые тесты тоже были очень красивые, но когда включил рабочее приложение все производительность исчезла и осталось порядка 10% прироста, что для меня было не приемлемо.

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

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

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

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

Если же memcached используется как хранилище динамического контента (сгенерили страничку — положили в кэш), то тут все понятно.
У каждого результата есть заголовок. Там написано «с включенным OPcache»
Извиняюсь, ночью было дело.
Тогда удивлён, почему 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, снижая таким образом нагрузку на процессор.
ну на самом деле если скрипт все время висит в памяти и только в одном экземпляре эффект от opcache незначительный.

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

Articles