image

Настанет день, и ты поймешь, что одного потока в 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 и яростно архитектурим систему. А хотим мы следующее:

image

  • 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)


  1. ZOXEXIVO
    05.05.2015 12:42
    -6

    Цвет подобран прекрасно. Намекает…


    1. EminH
      05.05.2015 14:17
      +9

      Хм, что все в шоколаде? :)


      1. vba
        06.05.2015 11:58
        -4

        Шоколад бывает разный, бывает из глаза, бывает из Бельгии…


    1. limitium Автор
      05.05.2015 14:47
      +27

      Каждый видит через призму своего опыта…


  1. evnuh
    05.05.2015 14:27
    +2

    О чём статья? Что вы написали 3 строчки на PHP в роли демона для логгера, который не умирает, тем самым нарушая главный принцип PHP? Или что вы, опять же, при обработке запросов, куда-то коннектитесь через сокеты, обёрнутые ещё несколькими слоями, чтобы отправить лог? При чём здесь многопоточность? При чём здесь PHP в принципе? Или вы хотели рассказать про IPC для PHP, что само по себе абсурдно, опять же, из-за парадигмы недолгоживущих процессов php?


    1. limitium Автор
      05.05.2015 14:45
      +4

      Ваш главный принцип «PHP должен умирать»? Это нормально с этим можно продолжать жить.


    1. Fedot
      05.05.2015 16:15
      +5

      Пора выйти из танка и оглянуться вокруг.
      PHP уже с 5.3 прекрасно работает как долгоживущий процесс. А с выходом PHP 7, появиться возможность не падать при фатальных ошибках и проблем с долгой жизнью процессов не останется вовсе.


      1. karser
        05.05.2015 17:02

        Демон упал — можно подстраховать через upstart.


      1. evnuh
        05.05.2015 17:46
        +3

        Я недоумевал ни сколько из-за того, что PHP не должен жить, а из-за того, что человек написал статью как установить 0MQ, биндинги в PHP и написать 3 строчки кода, сделав демона логгера на этом самом PHP, хотя эти три строчки демона можно было написать на чем-то более подходящем, без установки биндингов и веб-серверов, например. Посередине статьи автор заикнулся про свой набор паттернов, не написав зачем и почему и куда.


      1. Ugputu
        06.05.2015 09:20
        +2

        уже сейчас можно не падать при фатальных ошибках, по крайней мере можно сделать свой обработчик и при большом желании возвращать управление из него основному процессу.


  1. Casus
    05.05.2015 14:49
    +5

    Как то большего ожидал от поста, надеялся увидеь Akka на php что-ли…


    1. limitium Автор
      05.05.2015 14:56
      -1

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


      1. Casus
        05.05.2015 15:40
        +5

        Лёгкость была бы в отлаженном распределённом фреймворке, а строить самому из сокетов и очередей, тема избитая, да и простого я тут не вижу.
        Если вы хотели просветить юных разрабов, то статье объёмней стоило быть.

        А на Dnode не смотрели? Ноды можно писать на любом языке.


  1. karser
    05.05.2015 17:00

    Однажды сталкивался с подобной задачей, пришел к выводу, что redis pub/sub подходит лучше. Заодно в редисе таски создавать можно и логи вести через monolog. И расширение компилить не надо — php-redis ставится из пакетов.


    1. aleks_raiden
      06.05.2015 17:00

      Этот вариант хорош, хотя в некоторых случаях имеет концептуальные проблемы. К примеру, если у вас несколько нод, то сообщения будут всегда рассылаться на все ноды, даже если одна из них, к примеру, никак не участвует. Кроме этого, в некоторых случаях следует быть осторожным, если у вас множество каналов — сложность publish растет O(n+m). Третьим моментом может быть то, что подписка требует выделенного соединения и не может использовать уже существующее (например, приложение использует редис для хранения каких-то данных). Ну и данные не сохраняются, поэтому схема в таком виде может применяться только если есть хоть один читатель (или эмулировать через другие команды для персистентной очереди)

      Вместе с этим, Redis pub/sub очень легкое и хорошее решение, использую сам в продакшине уже несколько лет, только позитивные впечатления.


  1. ZhukV
    05.05.2015 17:06
    +3

    ZeroMQ достаточно хорошая вещь, но это все равно реализация AMQP, всего лишь за исключением нескольких плюшек.
    Если же говорить о AMQP, а именно о этом подходе, то здесь наверное для простых задач и кролик справиться на 5+, учитывая, что из-за exchange (точка обмена, что Вы «обосрали»), есть куча разных возможностей, в том числе и обработка одного и того же сообщениями разными воркерами.
    Также, следует посмотреть в сторону Gearman, у него большая плюшка в том, что можно просмотреть статус выполнения задачи, что очень даже не хватает.

    Сначала ты оптимизируешь код, потом пытаешься изменить сознание на асинхронный реакт, но весь PHP мир не хочет понимать такое стремление.


    Откуда Вы это взяли? Посмотрите сколько форков и звезд на репе реакта (не так то и много, но и не мало, чтобы не дать этому уважение).

    P.S. В общем, ожидал от статьи больше. Это как на конфах часто видно название доклада «Ассинхроность в PHP», а начинают рассказывать там о разных RabbitMQ, ActiveMQ, etc…


    1. Googolplex
      05.05.2015 17:21
      +1

      ZeroMQ — это реализация AMQP? o_O


      1. 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) один и тот же.


  1. FractalizeR
    05.05.2015 18:02
    +1

    Мне было не очень понятно, почему ZeroMQ попал в один список с

    RabbitMQ, ActiveMQ, Kafka, Kestrel и даже Redis.
    . Ведь ZeroMQ — это, как вы правильно заметили, только лишь «набор сокетов на стероидах». Как же его можно сравнивать с Kafka, скажем или с RabbitMQ…


  1. necromant2005
    05.05.2015 19:24
    +2

    Самый большой вопрос в данном случае что происходит когда демон умирает или связь затыкается. Такая простая реализация на коленке конечно будет работать, но в реальности нужно делать heartbeat, продумывать и реализовывать свой протокол с обработкой ошибок, обработкой дубликатов, ресабмит данных в очередь, шардинг тасков через воркеры динамически и т.д. Реальность достаточно сложная штука, еще следить за тем как закрываются и обрабатываются соединения в самом php и избегать блокировки ресурсов или снова наворачивать еще микро-очереди-сервисы для блокирующих операций.
    Крайне рекомендую посмотреть доклад Алексея Качаева про ZeroMQ для того чтоб понять что все это далеко не так просто как написать 3 строчки кода и что люди используют готовые сервера очередей не просто так: www.youtube.com/watch?v=d26LufnoQ4I


    1. limitium Автор
      05.05.2015 19:35

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


      1. necromant2005
        05.05.2015 19:41
        +2

        Это не «ентерпрайзность» — это необходимый минимум


  1. REZ1DENT3
    05.05.2015 20:24
    +1

    Для асинхронных задач есть nodejs, Java, phpDemon… Так извращаться можно только если проект уже есть и он большой, и переписывать его лень.

    ЗЫ, сам люблю PHP. Автору спасибо, библиотеку посмотрю.


  1. Syra
    05.05.2015 23:06

    Друг, мне кажется, ты хочешь сделать из php то, чем он не является. Попробуй erlang. Выглядит так, как будто ты хочешь писать код именно в его стиле.
    Если же ты хотел познакомить аудиторию с ZeroMQ, то выбрал весьма неудачный способ. 0MQ — решение для узкого круга задач, когда нам не нужна гарантия выполнения, не нужны очереди (или мы готовы писать эти вещи сами). Зачем они вообще нужны? Вот хорошее описание (раздел ack).
    Строить на 0MQ асинхронный php фреймворк? Во-первых, асинхронные фреймворки есть, и они работают. Во-вторых, есть hack. Предлагаю посмотреть на его реализацию асинхронности и прикинуть – может это именно то, что нужно? Набор хороших библиотек, и, я уверен, код на hack будет так же сексуален, как генераторы на node.js (описание проблемы управления flow в event-driven programming, библиотека (внимание на yield)).
    Можно спросить, «а где тут взаимодействие между серверами на php?». Можно написать самому. А можно использовать какой-нибудь *MQ с очередями.


  1. Rathil
    06.05.2015 00:21

    Столкнулись как-то мы на работе с проблемой, что нужно обрабатывать очень много записей в БД, при простом нажатии на кнопку в UI-е (мы пишем MDM систему).
    Ну а обработать нужно сейчас, а не через время, так что крон и отложенная задача отпала. Смотрели в сторону MQ систем, да, решения есть, но нас они целиком не устроили. Решили писать своего Queue-шного демона на QT, который по своему протоколу ждёт команду с дальнейшими указаниями, что ему делать дальше. После получения запроса из веба (мы на yii пишем) мы обрабатываемых запрос и все критические данные сохраняем, а после делаем обращение к нашей очереди (которая при необходимости умеет агрегировать запросы, если это разрешается по протоколу) и спокойно завершаем процесс. Очередь же осуществляет новый запрос к серверу для «продолжения» прошлого запроса и обработки долгих операций (а операции реально долгие).
    Вся эта кухня предоставляется как инхостовое решение под windows, или как облочное решение (saas) в амазоне.
    На винде queue-шный демон оформлен ввиде службы, а на линухе ввиде сервиса.
    Чуть позже было принято решение докрутить туда ещё и аналог крона, для единой точки управления отложенных задач и «параллельных» долгих вычислений.


    1. Rathil
      06.05.2015 00:24

      Так мы ушли от идеи демона на php.


  1. leooverlight
    06.05.2015 03:48

    Настанет день, и это случится.

    Сначала ты читаешь первые пару абзацев и замечаешь удивительное сходство речевых оборотов автора и одного из твоих знакомых программистов. «Хех, прикольно» — думаешь ты. Дочитав до середины статьи, ты уже не на шутку удивлён сходством не только оборотов, но и манеры изложения. «Возможно, это особый стиль общения в какой-то тусовочке» — строишь ты предположение. Затем ты дочитываешь до конца и решаешь всё-таки узнать, кто же этот автор, что его манера разговора настолько популярна…

    image

    P.S. Димону привет передавай. =)


    1. mtp
      06.05.2015 16:37

      «Даааа, Петька, раскидала нас Гражданская»


  1. 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
    Если менее секунды, то да, нужен сервер очередей.


    1. p4s8x
      12.05.2015 02:49

      Ааа, я думал так с supervisord только я извращаюсь
      У нас таким образом организована синхронизация из кэша некритичных данных в основную БД. Используем redis SET для того, чтобы контроллировать уникальность сообщений в очереди. Сообщение — id записи, которую надо отправить в БД. Вот думаем, как более красиво можно решить данный момент.


  1. PerlPower
    06.05.2015 16:39

    А чем так плохи MQ с брокерами?