Предыстория
В процессе изучения работы 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.
Запуск 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/).
Однако все они показались мне слишком замудрёнными и требующими какого-то прям длительного и глубокого изучения их возможностей прежде чем что-то осмысленно с их помощью делать.
Да и как-то это, наверное, перебор — использовать таких тяжеловесов лишь для того, чтобы с нуля не создавать простого демона. :-)
Хотя… ;-)
Комментарии (4)
balpom Автор
04.06.2024 08:03Нужно прокинуть через bash до php прохождение posix signal
Огромнейшее спасибо за Ваш комментарий!
Я прочитал всё, что изложено на странице по Вашей ссылке.
Однако мне не хватает ума понять вот что:
вот внутри себя Workerman запускает процессgnome-terminal -- php bin/start_worker
посредством PHP-функции proc_open, shell_exec или ещё как-то - неважно.
Никак не пойму, каким боком тут оказывается bash... Исходный же скрипт, которым внутри себя Workerman запускает gnome-terminal - он же не bash, а php...И, соответственно, читая про все эти перехваты сигналов bash'евской функцией trap, никак не могу понять, каким боком оно применимо к моей ситуации.
Я пробовал запускать и как
bash -c "gnome-terminal -- php bin/start_worker"
, и какbash -c "gnome-terminal -- php bin/start_worker & wait"
и ещё как-то.
И из скрипта а-ля bash bin/gnome.sh
#!/bin/bash
gnome-terminal -- php bin/start_worker
exit 0
пробовал запускать...И оно даже запускается. Но всё также при прекращении работы Symfony Worker'ов запустившие их Workerman'овские Worker'ы не падают (и, соответственно, не перезагружаются Workerman'ом).
Наверное, я в принципе не понимаю смысл фразы "прокинуть через bash до php прохождение posix signal"...
Если можно - приведите, пожалуйста, пример такой волшебной команды (или иного шаманского действия).....vladdnepr
04.06.2024 08:03Нужно запускать без прослойки в виде bash или gnome-terminal
https://github.com/balpom/symfony-messenger-and-workerman/blob/main/bin/runner#L21Нужно просто почитать документацию как рекомендуют работать
https://symfony.com/doc/current/components/process.html#using-features-from-the-os-shell
https://symfony.com/doc/current/components/process.html#process-signals
balpom Автор
04.06.2024 08:03Нужно запускать без прослойки в виде bash или gnome-terminal
Да это-то понятно... Без "прослойки" оно работает. С "прослойкой" хотелось для наглядности.
В-общем, задачу запуска с "прослойкой" в виде Gnome Terminal я решил
довольно элегантнымабсолютно варварским способом: :-)$pid = posix_getpid(); // Current Workerman's Worker PID. $line = 'bash -c "gnome-terminal --wait -- php bin/start_worker; kill -SIGQUIT ' . $pid . '"'; $process = Process::fromShellCommandline($line); $process->run();
Что тут делается:
определяем PID текущего Workerman Worker'а
запускаем gnome-terminal, в котором запускаем Symfony Worker
-
следующей командой ставим kill, которому динамически подсовываем PID текущего Workerman Worker'а
gnome-terminal обязательно запускаем с опцией --wait.
Это важно, т.к. иначе сразу же исполняется следующая команда. А так терминал ждёт, пока завершится его дочерний процесс (Symfony Worker).Почему нельзя просто запустить терминал, без bash -c ?
Я пробовал, но при этом вываливается ошибка
sh: 1: kill: Illegal option -S
in /var/www/ ... /vendor/symfony/process/Process.php:270Детально разбираться что к чему я не стал, но, глядя на текст ошибки, интуитивно понятно, что если запускать терминал не из bash ("напрямую"), то так каким-то образом используется оболочка sh, в которой, видимо, другой синтаксис. Ну и, соответственно, надо запускать через bash...
В-общем, пример я обновил: https://github.com/balpom/symfony-messenger-and-workerman
Теперь командыphp bin/start
иphp bin/stop
работают с Gnome Terminal как задумано. :-)Да: в процессе тестирования выяснилось, что старая версия bin/stop работает неправильно.
Поменял местами команды остановки Worker'ов - теперь сначала останавливаются Worker'ы Workerman'а, а потом - Worker'ы Symfony.Как-то так...
vladdnepr
Особенность запуска с прослойкой в виде bash
https://gist.github.com/portante/e81bc6b8e7560a6b3d9dd1acfdd4d427
Нужно прокинуть через bash до php прохождение posix signal