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

от автора

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

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

Предисловие

Сделано 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 сокета (от клиентов), генерируется сообщение, содержащее в себе уникальный код клиента. Далее создается пакет для запроса в сервис, состоящий из названия сервиса, номера клиента и остальных данных. Схематически это можно представить так:

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

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

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

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

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

Параметры

 USAGE:      waha-proxy-balance  [-b <zmq bind>] [-v] -n <name> [-u <zmq bind>] [--]                        [--version] [-h]   Where:      -b <zmq bind>,  --backend <zmq bind>      Backend binding for proxy     -v,  --verbose      Show much more output     -n <name>,  --name <name>      (required)  Balancing service name     -u <zmq bind>,  --url <zmq bind>      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 <ms>] [--threads <int>] [-p <int>] [-v] [-u <zmq                     bind>] [--] [--version] [-h]   Where:      -t <ms>,  --timeout <ms>      Request timeout  without `timeout` param. -1 - infinity     --threads <int>      HTTP max threads     -p <int>,  --port <int>      HTTP binding port     -v,  --verbose      Show much more output     -u <zmq bind>,  --url <zmq bind>      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 <name>                         [-v] [-u <zmq bind>] [--] [--version] [-h] <string>                         ...   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>,  --name <name>      (required)  Balancing service name     -v,  --verbose      Show much more output     -u <zmq bind>,  --url <zmq bind>      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.     <string>  (accepted multiple times)      Predefined args for script      Broker script service 

Код здесь на GitHub

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

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

Параметры

 USAGE:      waha-cli  [-t <ms>] -n <name> [-v] [-u <zmq bind>] [-p] [-e <plain              |base64|hex>] [--out-sep <char>] [-o <empty|plain|delimited              |json>] [-d <plain|base64|hex>] [--in-sep <char>] [-i <args              |plain|delimited|json>] [--] [--version] [-h] <string> ...   Where:      -t <ms>,  --timeout <ms>      Request timeout. -1 - infinity     -n <name>,  --name <name>      (required)  Remote service name     -v,  --verbose      Show much more output     -u <zmq bind>,  --url <zmq bind>      Broker url     -p,  --pretty      Pretty JSON output for 'json'     -e <plain|base64|hex>,  --encoder <plain|base64|hex>      Output encoder for 'delimited' output     --out-sep <char>      Output separator     -o <empty|plain|delimited|json>,  --output <empty|plain|delimited|json>      Output mode in CLI mode     -d <plain|base64|hex>,  --decoder <plain|base64|hex>      Input decoder for 'delimited' input     --in-sep <char>      Input separator     -i <args|plain|delimited|json>,  --input <args|plain|delimited|json>      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.     <string>  (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. Дабы код не пропадал, выложил его в общий доступ.
Если кому нужно это — обращайтесь проконсультирую.

Будете использовать?

Никто ещё не голосовал. Воздержавшихся нет.

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

ссылка на оригинал статьи http://habrahabr.ru/post/262977/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *