Настанет день, и ты поймешь, что одного потока в PHP тебе мало.
Сначала ты оптимизируешь код, потом пытаешься изменить сознание на асинхронный реакт, но весь PHP мир не хочет понимать такое стремление. Смотришь на phthreads, но после java concurrency чувствуешь, что тебя где-то “обманули”. А когда ты задумаешь покинуть процесс и начнешь захлебываться в форках exec’ах и сигналах, ты поймешь, что дальше погружаться нельзя. И, наконец, всплыв из всего этого, ты поплывешь к острову MOM (message-oriented middleware).
Оооо, а каких тут продавцов кирби только нет: RabbitMQ, ActiveMQ, Kafka, Kestrel и даже Redis pub/sub’ом подбарыживает. И у всех все хорошо: все самое лучшее, быстрое, безотказное. Но есть небольшая беда — шаг в сторону и, привет, теперь ты в числе толпы нытиков на stackoverflow в поисках воркэраундов и странных схем. И это будет продолжаться пока ты не найдешь ZeroMQ.
А что это? ZeroMQ — высокопроизводительная асинхронная месседж-ориентированная библиотека направленная на использование в масштабируемых распределенных или параллельных приложениях. Но в отличии от других месседж-ориентированных мидлваре она безброкерная и работает без сервера, как такового.
И что же тут предлагают тогда? А предлагают эти ребята набор сокетов на стероидах оптимизированных под основные месседж паттерны и использовать мы их можем как хотим. С их помощью можем построить сеть с любой топологией и сложностью.
Еще у них есть своя сектанская дока http://zguide.zeromq.org/page:all. Хорошо вправляет мозги в нужном направлении независимо от того, будешь использовать 0mq или нет, правда, если можешь в многопоточное программирование, можно частично пролистывать.
В сухом остатке:
- Набор стероидных сокетов
- Чертовски быстрые
- Работа через IPC,TCP, multicast, inproc
- Асинхронные
- Легкий старт
- Байндинги для овер 40 языков программирования
Звучит все это очень круто! Хватит теорий идем на www.gliffy.com и яростно архитектурим систему. А хотим мы следующее:
- Auth + task generator
Отвечает за авторизацию на сервере и раздает нон-стоп задачи на парсинг. - Parse worker
Получает ключ авторизации и задачу для парсинга после завершение возвращает результат генератору. - Parsed data publisher
Получив результат от воркера, паблишит его всем сабскрайберам. - Alert system subscriber
Получает данные и рассылает алерты, если нужно. - Upload system subscriber
Получает данные и заливает их на сервер. - Data upload worker
Енкодит, зипует и аплодит данные на сервер. - System monitor
Собирает статистику системы в рейалтайме.
Архитектура получилась несложная, все компоненты были написаны отдельно от бизнес-логики, оформлены в пакет для композера и запаблишены на гитхаб:
https://github.com/limitium/zmq
Чтоб поковырять, нужно (на примере дебиан):
1. Установить ZeroMQ
sudo apt-get update -qq
sudo apt-get install -y libzmq3-dev
2. Установить байндинг php-zmq
git clone https://github.com/mkoppanen/php-zmq.git
sh -c "cd php-zmq && phpize && ./configure && make --silent && sudo make install"
echo "extension=zmq.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`
3. Установить либу через composer
composer require limitium/zmq
Дальше берем, например, psr-3 логгер и смотрим, как он работает:
Логгер
$logger = new ZLogger('my_service_1', 'tcp://127.0.0.1:5555');
$logger->info("core is stable");
$logger->emergency("we're all going to die!");
Сборщик логов
(new Concentrator('tcp://127.0.0.1:5555'))
->setReceiver(function ($logMsg) {
$serviceName = $logMsg[0];
$time = $logMsg[1];
$logLevel = $logMsg[2];
$logMsg = $logMsg[3];
})
->listen();
Все просто, при этом логгер благодаря плюшкам ZeroMQ может работать как в рамках одного процесса, так и собирать информацию со 100500 серваков.
Пример генератора задач и воркера
Generator
(new Ventilator('tcp://127.0.0.1:5555'))
->setGenerator(function () {
sleep(1);
return rand();
})
->setResponder(function ($msg) {
echo $msg;
})
->listen();
Worker
(new Worker('tcp://127.0.0.1:5555'))
->setExecutor(function ($msg) {
return $msg + $msg;
})
->work();
И под конец банальный pub/sub
Publisher
$pub = new Publisher('tcp://127.0.0.1:5555');
$pub->send('azaza');
Subscriber
(new Subscriber('tcp://127.0.0.1:5555'))
->setListener(function ($msg){
echo $msg;
})
->listen();
Единственный минус ZeroMQ, который стоит отметить — чем больше интерпрзайности хочется от системы, тем больше придется писать кода. Но кого это волнует, когда запускается все в 2 строчки кода?
Комментарии (31)
evnuh
05.05.2015 14:27+2О чём статья? Что вы написали 3 строчки на PHP в роли демона для логгера, который не умирает, тем самым нарушая главный принцип PHP? Или что вы, опять же, при обработке запросов, куда-то коннектитесь через сокеты, обёрнутые ещё несколькими слоями, чтобы отправить лог? При чём здесь многопоточность? При чём здесь PHP в принципе? Или вы хотели рассказать про IPC для PHP, что само по себе абсурдно, опять же, из-за парадигмы недолгоживущих процессов php?
limitium Автор
05.05.2015 14:45+4Ваш главный принцип «PHP должен умирать»? Это нормально с этим можно продолжать жить.
Fedot
05.05.2015 16:15+5Пора выйти из танка и оглянуться вокруг.
PHP уже с 5.3 прекрасно работает как долгоживущий процесс. А с выходом PHP 7, появиться возможность не падать при фатальных ошибках и проблем с долгой жизнью процессов не останется вовсе.evnuh
05.05.2015 17:46+3Я недоумевал ни сколько из-за того, что PHP не должен жить, а из-за того, что человек написал статью как установить 0MQ, биндинги в PHP и написать 3 строчки кода, сделав демона логгера на этом самом PHP, хотя эти три строчки демона можно было написать на чем-то более подходящем, без установки биндингов и веб-серверов, например. Посередине статьи автор заикнулся про свой набор паттернов, не написав зачем и почему и куда.
Ugputu
06.05.2015 09:20+2уже сейчас можно не падать при фатальных ошибках, по крайней мере можно сделать свой обработчик и при большом желании возвращать управление из него основному процессу.
Casus
05.05.2015 14:49+5Как то большего ожидал от поста, надеялся увидеь Akka на php что-ли…
limitium Автор
05.05.2015 14:56-1Смысл поста показать легость вхождения в распределенный PHP с небольшим количеством примеров(сейчас еще добавлю из гитхаба). Проблема в том, что если написать кучу универсальных брокеров это превратится в еще один RabbitMQ
Casus
05.05.2015 15:40+5Лёгкость была бы в отлаженном распределённом фреймворке, а строить самому из сокетов и очередей, тема избитая, да и простого я тут не вижу.
Если вы хотели просветить юных разрабов, то статье объёмней стоило быть.
А на Dnode не смотрели? Ноды можно писать на любом языке.
karser
05.05.2015 17:00Однажды сталкивался с подобной задачей, пришел к выводу, что redis pub/sub подходит лучше. Заодно в редисе таски создавать можно и логи вести через monolog. И расширение компилить не надо — php-redis ставится из пакетов.
aleks_raiden
06.05.2015 17:00Этот вариант хорош, хотя в некоторых случаях имеет концептуальные проблемы. К примеру, если у вас несколько нод, то сообщения будут всегда рассылаться на все ноды, даже если одна из них, к примеру, никак не участвует. Кроме этого, в некоторых случаях следует быть осторожным, если у вас множество каналов — сложность publish растет O(n+m). Третьим моментом может быть то, что подписка требует выделенного соединения и не может использовать уже существующее (например, приложение использует редис для хранения каких-то данных). Ну и данные не сохраняются, поэтому схема в таком виде может применяться только если есть хоть один читатель (или эмулировать через другие команды для персистентной очереди)
Вместе с этим, Redis pub/sub очень легкое и хорошее решение, использую сам в продакшине уже несколько лет, только позитивные впечатления.
ZhukV
05.05.2015 17:06+3ZeroMQ достаточно хорошая вещь, но это все равно реализация AMQP, всего лишь за исключением нескольких плюшек.
Если же говорить о AMQP, а именно о этом подходе, то здесь наверное для простых задач и кролик справиться на 5+, учитывая, что из-за exchange (точка обмена, что Вы «обосрали»), есть куча разных возможностей, в том числе и обработка одного и того же сообщениями разными воркерами.
Также, следует посмотреть в сторону Gearman, у него большая плюшка в том, что можно просмотреть статус выполнения задачи, что очень даже не хватает.
Сначала ты оптимизируешь код, потом пытаешься изменить сознание на асинхронный реакт, но весь PHP мир не хочет понимать такое стремление.
Откуда Вы это взяли? Посмотрите сколько форков и звезд на репе реакта (не так то и много, но и не мало, чтобы не дать этому уважение).
P.S. В общем, ожидал от статьи больше. Это как на конфах часто видно название доклада «Ассинхроность в PHP», а начинают рассказывать там о разных RabbitMQ, ActiveMQ, etc…Googolplex
05.05.2015 17:21+1ZeroMQ — это реализация AMQP? o_O
ZhukV
05.05.2015 18:08Да, уж, сорри, ошибочка, сам не знал.
zeromq.wdfiles.com/local--files/intro%3Aread-the-manual/Middleware%20Trends%20and%20Market%20Leaders%202011.pdf
Указано, что они хотели бы его поддерживать, но он сильно громоздкой, по их мнению, и пока что они решили его не внедрять. Но опять же, документ датирован 2011 года (http://zeromq.org/intro:read-the-manual Comparisons)
Да, они не используют этот протокол, но методология (pattern) один и тот же.
FractalizeR
05.05.2015 18:02+1Мне было не очень понятно, почему ZeroMQ попал в один список с
RabbitMQ, ActiveMQ, Kafka, Kestrel и даже Redis.
. Ведь ZeroMQ — это, как вы правильно заметили, только лишь «набор сокетов на стероидах». Как же его можно сравнивать с Kafka, скажем или с RabbitMQ…
necromant2005
05.05.2015 19:24+2Самый большой вопрос в данном случае что происходит когда демон умирает или связь затыкается. Такая простая реализация на коленке конечно будет работать, но в реальности нужно делать heartbeat, продумывать и реализовывать свой протокол с обработкой ошибок, обработкой дубликатов, ресабмит данных в очередь, шардинг тасков через воркеры динамически и т.д. Реальность достаточно сложная штука, еще следить за тем как закрываются и обрабатываются соединения в самом php и избегать блокировки ресурсов или снова наворачивать еще микро-очереди-сервисы для блокирующих операций.
Крайне рекомендую посмотреть доклад Алексея Качаева про ZeroMQ для того чтоб понять что все это далеко не так просто как написать 3 строчки кода и что люди используют готовые сервера очередей не просто так: www.youtube.com/watch?v=d26LufnoQ4Ilimitium Автор
05.05.2015 19:35Все верно в примере с генератором и воркерами есть и хертбиты и проверка статусов, как генератора так и воркера с отслеживанием переходов из одного в другой и стратегия для реконнектов Но, как я писал выше, вся ентерпрайзность пишется руками и под конкретную задачу. Где-то можно пропускать месседжы, где-то нужно биться до последнего, где-то нужны синхронизации и транзакции. Все эти вещи определяются задачей, которую нужно решать. Увы такое не решается в общем виде.
Да доклад отличный в нем нет противоречий моим словам.
REZ1DENT3
05.05.2015 20:24+1Для асинхронных задач есть nodejs, Java, phpDemon… Так извращаться можно только если проект уже есть и он большой, и переписывать его лень.
ЗЫ, сам люблю PHP. Автору спасибо, библиотеку посмотрю.
Syra
05.05.2015 23:06Друг, мне кажется, ты хочешь сделать из php то, чем он не является. Попробуй erlang. Выглядит так, как будто ты хочешь писать код именно в его стиле.
Если же ты хотел познакомить аудиторию с ZeroMQ, то выбрал весьма неудачный способ. 0MQ — решение для узкого круга задач, когда нам не нужна гарантия выполнения, не нужны очереди (или мы готовы писать эти вещи сами). Зачем они вообще нужны? Вот хорошее описание (раздел ack).
Строить на 0MQ асинхронный php фреймворк? Во-первых, асинхронные фреймворки есть, и они работают. Во-вторых, есть hack. Предлагаю посмотреть на его реализацию асинхронности и прикинуть – может это именно то, что нужно? Набор хороших библиотек, и, я уверен, код на hack будет так же сексуален, как генераторы на node.js (описание проблемы управления flow в event-driven programming, библиотека (внимание на yield)).
Можно спросить, «а где тут взаимодействие между серверами на php?». Можно написать самому. А можно использовать какой-нибудь *MQ с очередями.
Rathil
06.05.2015 00:21Столкнулись как-то мы на работе с проблемой, что нужно обрабатывать очень много записей в БД, при простом нажатии на кнопку в UI-е (мы пишем MDM систему).
Ну а обработать нужно сейчас, а не через время, так что крон и отложенная задача отпала. Смотрели в сторону MQ систем, да, решения есть, но нас они целиком не устроили. Решили писать своего Queue-шного демона на QT, который по своему протоколу ждёт команду с дальнейшими указаниями, что ему делать дальше. После получения запроса из веба (мы на yii пишем) мы обрабатываемых запрос и все критические данные сохраняем, а после делаем обращение к нашей очереди (которая при необходимости умеет агрегировать запросы, если это разрешается по протоколу) и спокойно завершаем процесс. Очередь же осуществляет новый запрос к серверу для «продолжения» прошлого запроса и обработки долгих операций (а операции реально долгие).
Вся эта кухня предоставляется как инхостовое решение под windows, или как облочное решение (saas) в амазоне.
На винде queue-шный демон оформлен ввиде службы, а на линухе ввиде сервиса.
Чуть позже было принято решение докрутить туда ещё и аналог крона, для единой точки управления отложенных задач и «параллельных» долгих вычислений.
leooverlight
06.05.2015 03:48Настанет день, и это случится.
Сначала ты читаешь первые пару абзацев и замечаешь удивительное сходство речевых оборотов автора и одного из твоих знакомых программистов. «Хех, прикольно» — думаешь ты. Дочитав до середины статьи, ты уже не на шутку удивлён сходством не только оборотов, но и манеры изложения. «Возможно, это особый стиль общения в какой-то тусовочке» — строишь ты предположение. Затем ты дочитываешь до конца и решаешь всё-таки узнать, кто же этот автор, что его манера разговора настолько популярна…
P.S. Димону привет передавай. =)
la0
06.05.2015 11:33+2В 99% для подобных задач достаточно трёх компонентов:
— worker.php
— supervisord (или другой менеджер процесов) который запускает worker`ы в нужном количестве(хоть 1, хоть 100) и перезапускает при падении или плановом exit(0) через 10к итераций(на всякий случай, хоть и как уже сказали без этого можно обойтись)
— очередь (хоть файловая с изъятием в очередь конкретного воркера средствами mv, хоть rabbitmq, хоть mysql табличка с правильной логикой блокировок).
Каждый из трёх можно менять по вкусу (для меня главный критерий — maintainability, в т.ч. не усложнять систему без необходимости).
Для задач, где таск длится более 5 секунд эта комбинация показала себя как самая управляемая.
Если таск 1-3 секунды тоже ОК, но файловая очередь поменяна на кролика и/или mysql
Если менее секунды, то да, нужен сервер очередей.p4s8x
12.05.2015 02:49Ааа, я думал так с supervisord только я извращаюсь
У нас таким образом организована синхронизация из кэша некритичных данных в основную БД. Используем redis SET для того, чтобы контроллировать уникальность сообщений в очереди. Сообщение — id записи, которую надо отправить в БД. Вот думаем, как более красиво можно решить данный момент.
ZOXEXIVO
Цвет подобран прекрасно. Намекает…
EminH
Хм, что все в шоколаде? :)
vba
Шоколад бывает разный, бывает из глаза, бывает из Бельгии…
limitium Автор
Каждый видит через призму своего опыта…