Взаимодействие PHP и Erlang посредством RabbitMQ

Вступление


Чем больше программируешь на php, тем чаще попадаются задачи, для решения которых нужен демон на сервере. Да, конечно существует phpDaemon, cron или костыли, которые при каждом n-ом запуске скрипта вызывают какой-то определенный набор операций. Но когда мы говорим о проектах с нагрузкой больше, чем на обычном сайте, мы начинаем расстраиваться.

В одном из проектов для решения такой задачи мы решили использовать связку php+RabbitMQ+erlang. На php уже был написан необходимый функционал, нам надо было лишь разнести вызовы по времени и на разные машинки. Конкретно задача звучала так: написать парсер пользователей с внешнего хранилища данных и, самое главное, поддерживать актуальность данных, а в случае их изменения, посылать уведомления.

Исходные данные


— Rest-функция на php, добавляющая пользователя в избранное. Из этого списка в дальнейшем и будут формироваться пользователи, актуальность информации которых мы и будем поддерживать, а в случае изменения — посылать уведомления на клиента.
— Приложение на erlange, который должен координировать, каких пользователей мы сейчас обрабатывем и т.д. Обработка информации с внешнего источника осуществляется двумя путями — это парсинг html страниц или где это возможно — запросы api. Для обработки ответов используется rest-функция, написанная на php.
— Мы должны поддерживать возможность простого масштабирования на большое количество серверов. Список пользователей, которые надо парсить, находятся в очередях, формируемых RabbitMQ.

Задача номер 1 — настроить расширение php для работы с RabbitMQ


В первую очередь устанавливаем дополнительный программные пакеты:

apt-get install gcc
apt-get install php5-dev

Далее сама установка, найденная на просторах интернета:

#download and install the rabbitmq c amqp lib
wget https://github.com/alanxz/rabbitmq-c/releases/download/v0.5.1/rabbitmq-c-0.5.1.tar.gz
tar -zxvf rabbitmq-c-0.5.1.tar.gz
cd rabbitmq-c-0.5.1/
./configure
make
sudo make install
cd ..
#download and compile the amqp
wget http://pecl.php.net/get/amqp-1.4.0.tgz
tar -zxvf amqp-1.4.0.tgz
cd amqp-1.4.0/
phpize && ./configure --with-amqp && make && sudo make install
#Add amqp extension to php mods-availabile directory
echo "extension=amqp.so" > /etc/php5/mods-available/amqp.ini
#Enabled it in cli
cd /etc/php5/cli/conf.d/
ln -s ../../mods-available/amqp.ini 20-amqp.ini
php -m | grep amqp
#Enabled it in cli
cd /etc/php5/apache2/conf.d/
ln -s ../../mods-available/amqp.ini 20-amqp.ini
#restart Apache and than check phpinfo on web
service apache2 restart

Если вам повезло, то все установилось верно.

Задача номер 2 — установить RabbitMQ и web-панель управления им


sudo apt-get install rabbitmq-server

rabbitmq-plugins enable rabbitmq_management

rabbitmqctl stop

rabbitmq-server -detached

Далее по данному адресу вы получаете доступ к управления очередями, по умолчанию логин(guest) и пароль(guest)
ip.addres:15672/

Задача номер 3 — через php влиять на очереди RabbitMQ


//создание точки обмена
$rabbit = new AMQPConnection(array('host' => '127.0.0.1', 'port' => '5672', 'login' => 'guest', 'password' => 'guest'));
$rabbit->connect();
$testChannel = new AMQPChannel($rabbit);
$exchange = new AMQPExchange($testChannel);
$exchange->setName('logoooooo');
$exchange->setType(AMQP_EX_TYPE_DIRECT);
$exchange->declare();

//создания очереди
$testChannel = new AMQPChannel($rabbit);
$queue = new AMQPQueue($testChannel); 
$queue->setName("yyyyyyy2");
$queue->declare();


//привязываем очередь к точки обмена
$testChannel = new AMQPChannel($rabbit);
$queue = new AMQPQueue($testChannel); 
$queue->setName("yyyyyyy2");
$queue->bind('logoooooo'); 
$queue->declare();

//отправить сообщение на точку
$testChannel = new AMQPChannel($rabbit);
$exchange = new AMQPExchange($testChannel);
$exchange->setName('logoooooo');
$exchange->publish('pooooooooooooooooooooooooooooooo');


Задача номер 4 — Создать приложение на erlange используя rebar


Устанавливаем rebar
apt-get install rebar
mkdir 1
rebar create template=simpleapp   srvid=my_server46
rebar create template=simplesrv   srvid=my_server46


Задача номер 5 — Запускаем приложение на erlange


Сначала устанавливаем CMake:

apt-get install make


В Makefile прописываем следующее:

all:
	rebar compile

run:
	ERL_LIBS=deps erl +K true -name myapp_app@127.0.0.1 -boot start_sasl -pa ebin -s myapp_app -sasl errlog_type error


Строчка -pa ebin -s myapp_app означает, что мы запускаем ebin/myapp_app.erl и в нем функцию myapp_app:start().
ERL_LIBS=deps означает, что мы подгружаем все библиотеки, которые расположены в папке deps.

Задача номер 6 — подключить необходимые библиотеки для связи RabbitMQ и Erlang


В rebar.config помещаем следующее:

{deps, [
	{rabbit_common, ".*", {git, "git://github.com/jbrisbin/rabbit_common.git", {tag, "rabbitmq-3.0.2"}}}
]}.

{erl_opts, [
  debug_info, 
  compressed, 
  report, 
  warn_export_all, 
  warn_export_vars, 
  warn_shadow_vars, 
  warn_unused_function, 
  warn_deprecated_function, 
  warn_obsolete_guard, 
  warn_unused_import 
%  warnings_as_errors
]}.

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

apt-get install xsltproc
apt-get install zip

После заходим в папочку deps, которую создал rebar и, используя git, все скачиваем, а после устанавливаем:

cd deps
git clone https://github.com/rabbitmq/rabbitmq-erlang-client.git
git clone https://github.com/rabbitmq/rabbitmq-server.git
git clone https://github.com/rabbitmq/rabbitmq-codegen.git
cd rabbitmq-erlang-client
make

Задача номер 7 — из Erlangа получать сообщения из очередей RabbitMQ


Файл myapp_app.erl оставляем чуть чуть редактируем, чтобы можно было запускать из makefile, что мы написали:

-module(myapp_app).

-behaviour(application).

-export([start/0,start/2, stop/1]).

start() ->
  myapp_sup:start_link().

start(_StartType, _StartArgs) ->
    myapp_sup:start_link().

stop(_State) ->
    ok.


Файл myapp_sup.erl, отвечающий за наблюдение процессами, дописываем вызов нашего модуля из init:

-module(myapp_sup).

-behaviour(supervisor).

-export([start_link/0]).

-export([init/1]).

-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).

start_link() ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init([]) ->
    {ok, { {one_for_one, 5, 10}, [{my_server46_0, {my_server46, start_link, []},permanent, brutal_kill, worker, [my_server46]}]} }.


Модуль, отвечающий за связь с RabbitMQ:

-module(my_server46).
-behaviour(gen_server).

-include("deps/rabbitmq-erlang-client/include/amqp_client.hrl").

-define(SERVER, ?MODULE).

-export([start_link/0,main/0,loop/1]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

init(Args) ->
  main(),
  {ok, Args}.

main() ->
  {ok, Connection} =
    amqp_connection:start(#amqp_params_network{host = "localhost"}),
  {ok, Channel} = amqp_connection:open_channel(Connection),

  io:format(" [*] Waiting for messages. To exit press CTRL+C~n"),

  amqp_channel:call(Channel, #'basic.qos'{prefetch_count = 1}),
  amqp_channel:subscribe(Channel, #'basic.consume'{queue = <<"yyyyyyy2">>},self()),
  receive
    #'basic.consume_ok'{} -> io:fwrite(" _rec_main_ok_ "),ok
  end,
  loop(Channel),
  io:fwrite("begin~n", []).

loop(Channel)->
  receive
    {#'basic.deliver'{delivery_tag = Tag}, #amqp_msg{payload = Body}} ->
      Dots = length([C || C <- binary_to_list(Body), C == $.]),
      io:format(" [x] Received Body ~p~n", [Body]),
      receive
      after
        Dots*1000 -> io:format(" _loop_rec_after_ ~p",[0]), ok
      end,
      timer:sleep(3500),
      amqp_channel:cast(Channel, #'basic.ack'{delivery_tag = Tag}),
      loop(Channel),
      io:format(" [x] Done 3~n")
  end.



handle_call(_Request, _From, State) ->
    {reply, ok, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.



Тут все достаточно просто, мы подписываемся на очередь «yyyyyyy2»:

amqp_channel:subscribe(Channel, #'basic.consume'{queue = <<"yyyyyyy2">>},self())

Затем сообщаем RabbitMQ, что сообщение успешно обработано:

amqp_channel:cast(Channel, #'basic.ack'{delivery_tag = Tag})

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 36

    +1
    -module(my_server46).

    Какой ужас…

    Если честно, не понимаю, зачем для взаимодействия php и erlang нужен RabbitMQ? Что бы периодически что-то передавать в erlang достаточно post/socket запроса из php и ranch в erlang. Обмен можно сделать в json или MessagePack . Из вашего примера не понятно зачем вам каналы. Если же они всё-таки нужны, то можно рулить ими на уровне транспорта ranch передавая канал в качестве одного из параметров запроса. В общем, ИМХО, забивание гвоздей микроскопом…
      0
      В принципе вы правы, в одном из проектов я так и делал, из erlanga вызывал php и нормально все обменивалась, просто если более детально описывать задачу, там была проблема, что не хватало мощностей все обрабатывать на одном сервере, и для этого мне и понадобился RabbitMQ, чтобы разнести задачи на несколько серверов.
      module(my_server46). — ну это да ужас, но вы же понимаете, что это код не с production версии, а просто для тестирования, написанный с нуля, чтобы быть уверенным, что все действительно будет работать + код production мне бы никто и не дал выложить.
        0
        Эм, я как-то не очень понимаю при чём здесь RabbitMQ и масштабирование? Вы масштабируете RabbitMQ вместо своего приложения? Или у вас нет функционала управления очередями? Что мешает взять какой-нибудь готовый пул, например, разнести своё приложение на несколько серверов, и на каждом используя пул воркеров обрабатывать запросы? При этом работу с очередью возьмёт на себя сам пул.
          0
          Это да, просто у нас в компании мало erlang разработчиков, и некоторые обработчики очередей мы планировали реализовывать средствами Python или Java.
            +4
            Вы даете неправильные и глупые советы. Что значит " Вы масштабируете RabbitMQ вместо своего приложения". Это вообще чушь!
            Вы предлагаете сочинять свой пул воркеров..? Чем для этого не подошел RabbitMQ?

            RabbitMQ — отличное средство для распределения задач между процессами и серверами. И не нужно изобретать свои поделки. А особенно не нужно писать в комментах призывы к их изобретению.

            Автор правильно поступил, что взял готовое средство, а не потратил деньги заказчика и нервы того, кто будет дорабатывать проект.
              –1
              Научитесь читать! В каком месте я предлагаю запилить свой пул? Из текста поста я могу сделать только один выводд — RabbitMQ для решения этой задачи избыточен! Для того чтобы послать сообщение из php в erlang RabbitMQ не нужен! В посте нет ни слова о том, что используется множество очередей, нет чёткого описания задачи, нет предпосылок к использованию RabbitMQ.
                0
                Честно говоря, не хотел грузить особенно описанием задачи, и последовательностью принятия тех или иных решений. Возможно в следующей статье буду использовать спойлер с максимально, на сколько это возможно, детальным описанием проекта в целом.
                  0
                  детальным описанием проекта в целом

                  Не совсем так. Детальное описание проекта не нужно. Нужно детальное описание задачи. Ведь ваше приложение решает определённую задачу. Достаточно было добавить картинку из коммента habrahabr.ru/post/251927/#comment_8309905 и всё было бы гораздо понятнее.
                  0
                  Вообще-то я ответил на Ваш комментарий, который относится не к самой статье, а к комментарию Alexc5c5c5, в котором он поясняет: «разнести задачи на несколько серверов».
                  Вы же в своем комментарии явно даете неправильные советы.
                    –1
                    Иллюзия… Я даю советы руководствуясь исключительно текстом поста и ветки комментариев. Если из текста и заголовка поста следует, что RabbitMQ используется исключительно для передачи сообщений между php и erlang, из-за того, что не справляется одна нода клиентского приложения на сервере, то это не повод ставить и настраивать RabbitMQ, а повод добавить ещё одну ноду и задуматься над производительностью всего приложения. Если же узким местом является стек сообщений каждого процесса, при этом каждое сообщение использует свой канал, то да, я согласен кролик частично снимет здесь проблему нагрузки, но тем не менее стоит задуматься над общей производительностью воркера.
            +2
            RabbitMQ (или другие MQ) как минимум потому что в этом случае нет привязки к конкретной реализации воркеров. В итогде спустя время воркеры можно реализовать на rust/go в особо критичных местах. Так же есть возможность маршрутизировать по приоритетам задачи и т.д. Словом… зачем изобретать для этого всего свой велосипед?
            0
            Это да, просто у нас в компании мало erlang разработчиков, и некоторые обработчики очередей мы планировали реализовывать средствами Python или Java
              0
              С помощью RabbitMQ в свое время реализовывал связь между PHP, Python и NodeJS. Прелесть в том, что я могу разнести worker'ов по серверам и при необходимости переписать его, используя другой язык программирования. Все зависит от задач.
                0
                Gearman не рассматривали как один из вариантов?
                  0
                  Буквально мельком, особенно не тестировал, меня удивило, что для его нормальной работы нужно дополнительно mysql. Плюс ходят слухи, что он медленнее.
                    0
                    Насколько я помню MySQL является только одним из вариантов хранения очередей и только если вам нужна надежность. Gearman предоставляет чуть больше возможностей (скажем отложенные задачи, выполнение задач по рассписанию и т.д.) и посему просто хранить очередь в файле, как это делает кролик, не очень продуктивно. Ну а писать для этого свою СУБД возможно просто накладно.
                      0
                      Просто хранить очередь в файле, как это делает кролик, не очень продуктивно — могу ошибаться, но FIFO в примитивном описании работает, как запись в начало файла, считывание с конца и все. А СУБД, в частности Mysql -> InnoDB например в конечном итоге записывает все в один файл, но тут мы тратимся дополнительно на саму прослойку взаимосвязи с СУБД.
                      Gearman предоставляет чуть больше возможностей — это да, реализовывать отложенные задачи на RabbitMQ приходится, немного костыльным способом.
                        0
                        Просто хранить очередь в файле, как это делает кролик, не очень продуктивно — могу ошибаться, но FIFO в примитивном описании работает, как запись в начало файла, считывание с конца и все.
                        Скажите это Apache Kafka, ага.
                          0
                          Под «не очень эффективно» я имел в виду не простые FIFO очереди а запуск тасков по расписанию и подобные штуки.
                            0
                            Запуск тасков по расписанию к mq не имеет отношения. И делать его через очередь не сильно далеко от тонзиллэктомии per rectum.
                              0
                              gearman не mq. Это job server. Так что все ок.
                                0
                                Я вас неправильно понял, извините. Думал, что вы про неэффективность реализации хранилища mq в виде файлового fifo, а не неэффективность реализацию job tracking через mq.
                          +1
                          > хранить очередь в файле, как это делает кролик

                          RabbitMQ хранит очередь в Mnesia, а не в файле
                    0
                    «REST-функция на PHP», «приложение на erlange», ну ёшкин кот, невозможно же читать. По сути: так и не понятно, чего добивались и чего добились. Ну хорошо, для чего-то использовали erlang-приложение для обмена сообщениями, и?..
                      0
                      Ну да, его для этого создавали вообще-то, в основном php мне нужен был по той причине, что приложение клиентов добавляла задачи используя rest. Плюс я для обработки, а точнее для для распаривания html страниц, так как erlang не очень хорошо умеет работать с текстом, а там достаточно сложная модель. По сути обработчик или говорил все ок, пользователь нечего не поменял, отправляй меня в конец очереди, или изменились данные и нужно сделать ещё 5-10 запросов к источнику информации, чтобы определить изменившиеся данные, ну и уведомление пользователю заодно посылает.
                      Сама идея использование Rest, в будущем позволит изменить обработчик, сделать его более эффективным, написать на любом языке.
                        0
                        > для для распаривания html страниц, так как erlang не очень хорошо умеет работать с текстом

                        В mochiweb есть полноценный html parser, вполне рабочий
                          0
                          Знаю о нем, но там кроме как распарсить html, нужно было регулярками пройтись и вырезать определенные кусочки, при разных условий, и использовать для этого erlang у меня не хватило времени.
                        0
                        В простейшем случае, это выглядит вот так
                        image"
                        Плюс дополнительно есть например очереди удаления пользователей, обработка которой полностью проходит без участия php, и другие…
                          0
                          Вы должны понимать, при такой архитектуре, я могу менять любой из этих модулей.
                          И один из бонусов, отказ 90% серверов с демонами Erlanga, повлияет в плане снижения скорости, но не более… RabbitMQ не дождется подтверждения, отправит задачи на другой сервер. Поверьте решения принимаются не просто так.
                          0
                          .
                            0
                            Интереса ради спрошу почему не написали консюмер на php?)

                            php есть у Вас в стеке технологий, amqp расширение уже стоит, для написания воркеров есть миллион туториалов, меня интересует кто и зачем принял это, на мой взгляд абсурдное, решение добавлять эрланг в стек технологий ради простейшей задачи, которую можно решить на php или любом другом языке из Вашего стека.
                              0
                              Была идея написать сначала на нем, чтоб вообще не париться, но через некоторое время, начинала течь память, да конечно оптимизировать и так далее, но все равно, php не очень хорошо для этого. Была статья habrahabr.ru/post/179399/, очень подробно расписывается данная проблема.
                              А вот почему Erlang, а не java например, вот это интересный вопрос :) Я конечно могу вам сказать, что в subscribe на Erlange сразу вызывается swapn для дальнейшей обработки, и таким образом скорость достаточно приличная получается…
                              Но в целом, вопрос религиозный, можно было и на чем то другом написать, ради разнообразия, да и сравнить консюмеры на разных языках, какой будет лучше справляться…
                              +1
                              > После заходим в папочку deps, которую создал rebar и, используя git, все скачиваем

                              зачем же это делать самому, если есть

                              rebar get-deps

                              ?
                                0
                                Далее возникли сложности с оставшимися библиотеками, поэтому пришлось использовать то, что написано на официальном сайте RabbitMQ

                                Как я понимаю вы предлагаете что-то типо вот этого
                                {deps, [
                                	{rabbit_common, ".*", {git, "git://github.com/jbrisbin/rabbit_common.git", {tag, "rabbitmq-3.0.2"}}},
                                	{rabbitmq_erlang_client, ".*", {git, "git://github.com/rabbitmq/rabbitmq-erlang-client.git", {tag, "rabbitmq_v3_0_2"}}},
                                	{rabbitmq_server, ".*", {git, "git://github.com/rabbitmq/rabbitmq-server.git", {tag, "rabbitmq_v3_0_2"}}},
                                	{rabbitmq_codegen, ".*", {git, "git://github.com/rabbitmq/rabbitmq-codegen.git", {tag, "rabbitmq_v3_0_2"}}}
                                ]}.
                                

                                Но это выдаст ошибку
                                Cloning into 'rabbitmq_erlang_client'…
                                ERROR: Dependency dir 1/deps/rabbitmq_erlang_client failed application validation with reason:
                                {missing_app_file,«1/deps/rabbitmq_erlang_client»}.

                                Как я понял на гите в папке src нет ни одного файла с расширением *.app.src. И собственно он конечно загрузит rabbit_common и даже rabbitmq_erlang_client, но далее не продвинется. И единственное решение, которое мне пришло это просто установить все, как написано в официальной инструкции www.rabbitmq.com/build-erlang-client.html. Если вы знаете, как это сделать через rebar, поделитесь.
                                  +1
                                  для клиента вполне достаточно

                                  {amqp_client, ".*" , {git, "git://github.com/jbrisbin/amqp_client.git", {tag, "rabbitmq-3.4.0-community"}}},
                                  


                                  больше ничего не надо
                                    0
                                    ну это форк, а не официальная версия, но в принципе они делают переупаковку, как я понимаю официальной версии, чтобы можно было, как раз использовать rebar. Спасибо, теперь жизнь станет проще!

                              Only users with full accounts can post comments. Log in, please.