Пример использования Workerman и Symfony Messenger

Результаты работы примера совместного использования Workerman и Symfony Messenger.

Результаты работы примера совместного использования Workerman и Symfony Messenger.

Предыстория

В процессе изучения работы PHP компонента Symfony Messenger (https://symfony.com/doc/current/components/messenger.html) мной был создан самодостаточный пример совместной работы Symfony Messenger и Symfony Console, подробно описанный в статье https://habr.com/ru/articles/817425/.

Для демонстрации работы этого примера нужно было вручную запустить несколько консолей (терминалов), а потом в каждой вручную запустить Worker.
Мой внутренний перфекционист :-) сильно против этого возражал и говорил «а вот бы все эти консоли-терминалы запускались одной командой, в нужном количестве, сразу с Worker«ами, а если какой Worker упадёт, то заново запускались в нужном количестве».

Возражать своему внутреннему перфекционисту я не стал и создал ещё один пример работы Symfony Messenger, который запускается Worker«ами из PHP фреймворка Workerman (https://github.com/walkor/workerman). При этом Symfony Console вообще не используется.

Подробнее о Workerman можно узнать здесь: https://manual.workerman.net/doc/ru/ (описание на русском языке).

Сам пример использования Symfony Messenger и Workerman

Как и в прошлом примере, очередь сообщений хранится в используемой через Doctrine базе данных SQLite.

Сам пример можно взять отсюда:
https://github.com/balpom/symfony-messenger-and-workerman

Либо можно установить через Composer:
composer create balpom/symfony-messenger-and-workerman

Как запустить пример

После установки откройте консоль и перейдите в созданную Composer’ом директорию symfony-messenger-and-workerman.
Выполните команду:
php bin/start
Эта команда запустит три простых Worker«а, имитирующих отправку SMS. Сейчас они ждут, когда в очереди появятся сообщения.
Количество Worker«ов настраивается в файле bin/runner.

Выполните команду:
php tests/sendmany.php
Эта команда запустит простой скрипт, который добавит в очередь несколько десятков сообщений.
После этого в ранее открытых консолях можно увидеть, как Worker’ы совместно «отправляют» SMS, берущиеся ими из очереди.

Выполните команду:
php bin/reload
Все Worker«ы «доотправят» взятые ими в работу SMS, завершат работу, запустятся заново и продолжат «отправлять» SMS (если они есть в очереди).

Выполните команду:
php bin/stop
Все Worker«ы «доотправят» взятые ими в работу SMS и завершат работу.

Тонкости реализации и выявленные недостатки

Призрак Symfony Console

Да, от компонента Symfony Console мы избавились. Однако его бледная тень в нашем примере незримо присутствует.
Дело в том, что класс SymfonyWorker, являющийся некой обёрткой для класса Symfony\Component\Messenger\Worker, примерно наполовину сделан на основе метода execute класса ConsumeMessagesCommand (Symfony\Component\Messenger\Command\ConsumeMessagesCommand).
Ну да, ничего умнее придумать не смог… ;-)

«Запускатор» для Workerman\Worker

Скрипт, запускающий аж целый asynchronous event-driven PHP framework with high performance, прост до безобразия и я позволю себе привести его здесь почти целиком (чуть позже мне это будет нужно ещё и для более простого описания выявленных недостатков).

 // bin/runner
namespace Balpom\SymfonyMessengerWorkerman;
use Workerman\Worker;
use Symfony\Component\Process\Process;
Worker::$daemonize = true; // Always run as daemon.
$worker = new Worker();
$worker->count = 3;        // Numbef of Workers.
// SymfonyWorkerFactory::getWorker(DIR . '/../config/dependencies.php')->run();
$process = new Process(['gnome-terminal', '--', 'php', 'bin/start_worker']);
$process->run();
};
Worker::runAll();

Прям «из коробки» эта конструкция понимает говорящие сами за себя команды
php bin/runner start
php bin/runner reload
php bin/runner stop

и некоторые другие (см. https://manual.workerman.net/doc/ru/install/start-and-stop.html).

Чтобы при запуске Workerman в режиме демона у команды «start» не указывать опцию »-d», в скрипте прописано Worker::$daemonize = true.

Работа команд php bin/runner start, php bin/runner reload и php bin/runner status. В

Работа команд php bin/runner start, php bin/runner reload и php bin/runner status.
В «Системном мониторе» видны соответствующие PHP-процессы.

Работа команды php bin/runner stop. В

Работа команды php bin/runner stop.
В «Системном мониторе» не видно ни одного PHP-процесса.
Команда php bin/runner status после остановки Workerman’а ничего не выводит.

Запуск Worker«ов «под микроскопом»

Если вы чуть более внимательно посмотрите на вышеприведённый код «запускатора», то увидите, что в своём примере Worker«ы, которые Worker«ы Symfony Messenger, а не Workerman«а, ;-) запускаются из Gnome Terminal.
Соответственно, если он у вас не установлен, то данный пример вам придётся адаптировать под ваши реалии.
Да, запускать всё это под Windows я не пробовал. Чёрт его знает, может, и будет работать, если как-то на запуск через команду «start» переделать…

Файл «bin/start_worker» запускает Symfony Worker и выглядит так:
namespace Balpom\SymfonyMessengerWorkerman;
SymfonyWorkerFactory::getWorker('/../config/dependencies.php')->run();

Файл «bin/stop_worker» останавливает Symfony Worker и выглядит так:
namespace Balpom\SymfonyMessengerWorkerman;
SymfonyWorkerFactory::getWorker('/../config/dependencies.php')->stopWorkers();

Файлы «bin/start» и «bin/stop» — это некий синтаксический сахар (чтобы поменьше символов в консоли набирать ;-)) и выглядят они так:
// bin/start
use Symfony\Component\Process\Process;
$process = new Process(['php', 'bin/runner', 'start']);
$process→run();

// bin/stop
$process = new Process(['php', 'bin/stop_workers']);
$process->run();
$process = new Process(['php', 'bin/runner', 'stop']);
$process->run();

Ну хорошо, хорошо… «bin/stop» — не совсем синтаксический сахар…
Как видно, он отдельно даёт команду на остановку Worker«ов Symfony Messenger, а потом уже Worker«ов Workerman«а.
Ну да, ничего умнее не придумал… ;-)

Файл «bin/reload» даёт команду на остановку Worker«ов Symfony и Worker«ов Workerman«а, а потом заново запускает workerman:
// bin/reload
$process = new Process(['php', 'bin/stop']);
$process->run();
$process = new Process(['php', 'bin/runner', 'start']);
$process->run();

Worker«ы Symfony, запущенные внутри терминалов, работают не так, как ожидалось

Да, конечно, возможность наблюдать работу Worker«ов в окнах терминалов — это наглядно и позволяет контролировать весь процесс.
Однако при тестировании этого всего столкнулся с тем, что если по каким-то причинам (не важно по каким — может, по таймауту / по числу обработанных message«s да или просто по kill) Worker Symfony Messenger прекратит свою работу, то новые консоли с Worker«ами Symfony не открываются.
При этом в «Системном мониторе» видно, что соответствующие PHP-процессы Worker«ов Workerman«а вполне себе живы-здоровы и умирать не собираются (если б умерли — то были бы автоматически перезагружены Workerman«ом и консоли бы открылись).
Как-то «правильно» запускать Workerman«ом из терминала Worker«ы Symfony у меня так и не получилось… :-(

Так как в реальных задачах вряд ли есть необходимость запускать Worker«ы именно в терминалах, то я не сильно-то и расстроился из-за вышеописанного непонятного поведения Worker«ов.
Тем более, что при запуске Worker«ов Symfony напрямую (закомментированная строка в коде «запускатора») они вполне себе работают как положено (ну да, работу по «отправке» не видно, ну да, можно было вывод не в консоль, а в файл выводить, но мне лень стало дальше этот пример усложнять пилить ;-)).

Послесловие

Используя Workerman (https://manual.workerman.net/doc/ru/), я смог как нефиг делать создать простого демона простым и понятным образом.

Я осведомлён о существовании AMPHP (https://amphp.org/), ReactPHP (https://reactphp.org/) и Swoole (https://openswoole.com/).
Однако все они показались мне слишком замудрёнными и требующими какого-то прям длительного и глубокого изучения их возможностей прежде чем что-то осмысленно с их помощью делать.
Да и как-то это, наверное, перебор — использовать таких тяжеловесов лишь для того, чтобы с нуля не создавать простого демона. :-)

Хотя… ;-)

© Habrahabr.ru