Темы



Брокер сообщений для сервисной архитектуры на базе ZMQ — или отдых разработчика

e7fddb3a90d44fc6a9b619abcad706df.jpg

Сильный ветер дул в борт судна. Мелкие брызги и капли дождя заставляли щурится слегка небритое лицо под очками. Было не просто холодно: холод проникал всюду. Под куртку, штаны. От него немели руки и застывала кровь. Но моряк знал, что где-то там за мысом есть тихий остров, на котором можно переждать непогоду.
Берег встретил измученный экипаж шумом деревьев и шепотом камышей. Люди знали, что у них есть лишь сутки, чтобы отдохнуть, помыться и продолжить борьбу со стихией.

Но в этот самый момент тишины и единения с природой, лишенный задач по выживанию, мозг одного из участников, программиста по жизни, стал с удвоенной силой искать проблемы. Это был мозг автора и он родил идею…

Сделано just-for-fun за 5 дней на борту судна со средней скоростью 7 узлов.

Профессиональное счастье программиста довольно простое — писать на своем любимом языке интересные задачи и получать за это деньги (желательно не маленькие, хотя денег всегда мало). Подобные желания привели к тому, что родился целый подход в виде отдельностоящих приложений и процесса обмена между ними: SOA (в частности SOAP/WSDL/XML-RPC/JSON-RPC т.п.), REST, микросервисная архитектура. Суть в том, что следуя заветам Unix, отдельный функционал выделяется в приложения, а обмен данными между ними специфицируется отдельно.
Одно из моих хобби связанно с работой распределенной сети мелких модулей: умный дом, система вычислений и другие схожие задачи. Для коммуникации между ними удобно использовать центральный брокер сообщений. Типовое решение: RabbitMQ, Redis, ActiveMQ и другие схожие решения. Из монстров индустрии можно отметить IBM Broker, IBM MQ, Tibco.
Но что-то мне в них не нравилось

  • Apache Active MQ и прочие Java-based брокеры. Минимум 100МБ на старте — злость берет даже при наличии 16Гб оперативы. Ничего против Java не имею, но все же…
  • IBM, Tibco и остальные. Было бы столько денег — потратил бы на что-нибудь еще.
  • Rabbit MQ, Redis. Возможно самый правильный вариант, но я же на отдыхе, а значит это не наш путь.


Выбор пал на собственную реализацию некого универсального, быстрого, с малым потреблением ресурсов, с простым протоколом роутера сообщений.

Коммуникационный слой в виде ZeroMQ для простоты обмена сообщениями.
С языком программирования возникли сложности. JVM-based, Python, Ruby отметаем по причине виртуальных машин, а значит избыточного потребления ресурсов. Хотел Rust, но после начала реализации понял, что надо ждать дальнейшей стабилизации стандартной библиотеки. Go — было бы отлично, если бы была родная реализация zmq. В итоге C++/C.

Первичная реализация

Брокер состоит из:

  • сервисов — именных клиентов (ZMQ_DEALER), работающих в режиме ответ-на-запрос
  • клиентов — анонимных клиентов (ZMQ_REQ), выполняющих запрос к сервису через брокер и ожидающих ответа.

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

99686faefe694a84938b0ed9aff5b2e7.png

Код можно посмотреть на GitHub.

Вторичная реализация

Реализация показалась очень простой. Надо было придумать и решить еще часть проблем.

Как использовать несколько экземпляров сервисов с одинаковым именем?


Допустим для балансировки нагрузки и отказоустойчивости.
Дело в том, что если используется одинаковое имя соединения в режиме DEALER-to-ROUTER, то использоваться будет только последнее подключение.
Решение — отдельный балансировщик нагрузки для каждого сервиса, подключающегося к брокеру и создающий отдельную точку соединения в режиме DEALER-to-DEALER. В этом случае, для сервисов достаточно лишь поменять адрес подключения на балансировщик вместо брокера.

Параметры
USAGE: 

   waha-proxy-balance  [-b ] [-v] -n  [-u ] [--]
                       [--version] [-h]


Where: 

   -b ,  --backend 
     Backend binding for proxy

   -v,  --verbose
     Show much more output

   -n ,  --name 
     (required)  Balancing service name

   -u ,  --url 
     Broker url

   --,  --ignore_rest
     Ignores the rest of the labeled arguments following this flag.

   --version
     Displays version information and exits.

   -h,  --help
     Displays usage information and exits.


   Broker balance module

Код здесь на GitHub

Как предоставить доступ к сервисам через HTTP?

Учитывая массовую любовь народа к HTTP интерфейсу, что, учитывая возможность работы хоть через умный тостер, не лишено смысла, надо это сделать правильно: json/plain вывод, поддержка как GET так и POST запросов, ну и JSONP на всякий пожарный.
С помощью библиотек Poco получилось реализовать в виде отдельного демона.

  • Параметр args в GET запросе или тело POST должно содержать JSON массив.
  • Опциональный параметр timeout — максимальное время ожидания ответа в мс.
  • Опциональный параметр type — тип возвращаемых данных — plain или json
  • Опциональный параметр jsonp или callaback — имя функции для JSON-P запроса
Параметры
USAGE: 

   waha-proxy-http  [-t ] [--threads ] [-p ] [-v] [-u ] [--] [--version] [-h]


Where: 

   -t ,  --timeout 
     Request timeout  without `timeout` param. -1 - infinity

   --threads 
     HTTP max threads

   -p ,  --port 
     HTTP binding port

   -v,  --verbose
     Show much more output

   -u ,  --url 
     Broker url

   --,  --ignore_rest
     Ignores the rest of the labeled arguments following this flag.

   --version
     Displays version information and exits.

   -h,  --help
     Displays usage information and exits.


   Broker HTTP proxy (REST like) module

Код здесь на GitHub

Почему Poco?

Почему не boost/libevent или еще что? Потому что Poco это очень тонкая обертка над системными вызовами, без лишнего обвеса, собирающееся с минимальными усилиями и отличной документацией. И просто она мне значительно понятнее чем boost.

Как сделать хорошо администратору и просто человеку, которому неохота программировать?

Делаем демона, который вызывает любую другую программу, передает ей на вход поля сообщения как аргументы и интерпретирует вывод как ответ. Упрощенный такой CGI.

Параметры
USAGE: 

   waha-service-script  [-s <script>] [--no-stdout] [-e] [-r] -n 
                        [-v] [-u ] [--] [--version] [-h] 
                        ...


Where: 

   -s <script>,  --script <script>
     Script path for 'script' mode

   --no-stdout
     Do not use stdout as message

   -e,  --stderr
     Append script stderr output

   -r,  --ret-code
     Append script return code as last field in message

   -n ,  --name 
     (required)  Balancing service name

   -v,  --verbose
     Show much more output

   -u ,  --url 
     Broker url

   --,  --ignore_rest
     Ignores the rest of the labeled arguments following this flag.

   --version
     Displays version information and exits.

   -h,  --help
     Displays usage information and exits.

     (accepted multiple times)
     Predefined args for script


   Broker script service

Код здесь на GitHub

Как сделать еще лучше?

Сделать доступ ко всем сервисам через консоль. С plain/hex/base64/json вводом и выводом.

Параметры
USAGE: 

   waha-cli  [-t ] -n  [-v] [-u ] [-p] [-e ] [--out-sep ] [-o ] [-d ] [--in-sep ] [-i ] [--] [--version] [-h]  ...


Where: 

   -t ,  --timeout 
     Request timeout. -1 - infinity

   -n ,  --name 
     (required)  Remote service name

   -v,  --verbose
     Show much more output

   -u ,  --url 
     Broker url

   -p,  --pretty
     Pretty JSON output for 'json'

   -e ,  --encoder 
     Output encoder for 'delimited' output

   --out-sep 
     Output separator

   -o ,  --output 
     Output mode in CLI mode

   -d ,  --decoder 
     Input decoder for 'delimited' input

   --in-sep 
     Input separator

   -i ,  --input 
     Input mode in CLI mode

   --,  --ignore_rest
     Ignores the rest of the labeled arguments following this flag.

   --version
     Displays version information and exits.

   -h,  --help
     Displays usage information and exits.

     (accepted multiple times)
     Args in request message for for 'args' input


   ZMQ broker console client interface


Код здесь GitHub

GIT + CMake + make

git clone https://github.com/reddec/waha.git && cd waha && mkdir build && cd build
cmake ../  -DCMAKE_BUILD_TYPE=Release
make && make package

Пакет для Debian будет в waha-*.deb, для остальных waha-*.zip

Авторизация пользователей с большой нагрузкой на чтение и небольшой на запись.

Запуск брокера (по умолчанию порт 10000)

$ waha-broker

Запуск балансировщика для чтения (по умолчанию порт 10001)

$ waha-proxy-balance -n system.user.check

Запуск httpasswd для проверки логин/пароля (запустить сколько угодно раз для распределения нагрузки). Выдает только код завершения работы. Все что идет после — — передается как аргументы скрипту.

$ waha-service-script  -u tcp://127.0.0.1:10001 -n system.user.check -s htpasswd  --ret-code --no-stdout -- -bv users.passwd

Запуск скрипта добавления пользователей (без балансировки) вместе с выводом

$ waha-service-script -n system.user.add -s htpasswd --ret-code --stderr -- -b users.passwd

Запуск HTTP интерфейса (порт по умолчанию 9001)

$ waha-proxy-http

пример запроса

http://127.0.0.1:9001/system.user.check?args=["admin","admin"]
http://127.0.0.1:9001/system.user.check?args=["admin","admin"]&callback=func123

Использование командного интерфейса

Вывод без форматирования:

$ waha-cli -n system.user.check admin admin


Вывод в JSON:

$ waha-cli -o json -n system.user.check admin admin

Модульный, работающий брокер сообщений, написанный во время отдыха для отдыха и развлечения программистом just for fun. Дабы код не пропадал, выложил его в общий доступ.
Если кому нужно это — обращайтесь проконсультирую.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

© Habrahabr.ru