
A strong wind blew into the side of the ship. Small splashes and raindrops made the slightly unshaven face squint under the glasses. It was not just cold: the cold penetrated everywhere. Under the jacket, pants. His hands were numb from him and the blood froze. But the sailor knew that somewhere there behind the cape there is a quiet island where you can wait out bad weather.
The shore was met by an exhausted crew with the noise of trees and the whisper of reeds. People knew that they had only a day to rest, wash and continue the struggle with the elements.
')
But at this very moment of silence and unity with nature, devoid of tasks for survival, the brain of one of the participants, a programmer for life, began to look for problems with redoubled force. It was the brain of the author and he gave birth to the idea ...
Foreword
Made just-for-fun in 5 days aboard a ship with an average speed of 7 knots.
Introduction
The professional happiness of a programmer is quite simple - to write interesting tasks in your favorite language and get paid for it (preferably not small, although there is always little money). Such desires led to the birth of a whole approach in the form of stand-alone applications and the exchange process between them:
SOA (in particular, SOAP / WSDL / XML-RPC / JSON-RPC, etc.), REST, micro-service architecture. The point is that following the precepts of Unix, a separate functionality is allocated to applications, and the data exchange between them is specified separately.
One of my hobbies is related to the work of a distributed network of small modules: smart home, computing system and other similar tasks. For communication between them, it is convenient to use a central message broker. Typical solution:
RabbitMQ , Redis,
ActiveMQ and other similar solutions. From the monsters of the industry can be noted
IBM Broker, IBM MQ ,
Tibco .
But I didn't like something about them.
"Fatal flaws" of existing solutions
- Apache Active MQ and other Java-based brokers. At least 100MB at the start - anger takes even if there is 16GB of RAM. I have nothing against Java, but still ...
- IBM, Tibco and the rest. It would be so much money - would spend on something else.
- Rabbit MQ, Redis . Perhaps the most correct option, but I'm on vacation, which means it is not our way.
The choice fell on our own implementation of a certain universal, fast, low-resource, simple protocol message router.
Components
Communication layer in the form of
ZeroMQ for easy messaging.
There are difficulties with programming language. JVM-based, Python, Ruby dismissed because of virtual machines, and therefore excessive consumption of resources. I wanted Rust, but after the start of implementation I realized that I had to wait for the further stabilization of the standard library. Go - it would be great if there was a native zmq implementation. As a result, C ++ / C.
Primary implementation
Broker consists of:
- services - nominal clients (ZMQ_DEALER), working in response-to-request mode
- clients - anonymous clients (ZMQ_REQ), making a request to the service through a broker and waiting for a response.
The connection point to the broker is a socket of type ROUTER. When receiving data from a socket REQ (from clients), a message is generated containing a unique client code. Next, a package is created for the request to the service, consisting of the service name, customer number and other data. Schematically, this can be represented as:

The code can be viewed on
GitHub .
Secondary implementation
The implementation seemed
very simple. It was necessary to invent and solve some more problems.
How to use multiple instances of services with the same name?
Valid for load balancing and fault tolerance.
The fact is that if the same connection name is used in the DEALER-to-ROUTER mode, then only the last connection will be used.
The solution is a separate load balancer for each service connecting to the broker and creating a separate connection point in the DEALER-to-DEALER mode. In this case, for services, it is enough to change the connection address to the balancer instead of the broker.
Options 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
The code here is on
githubHow to provide access to services via HTTP?
Considering the massive love of the people for the HTTP interface, which, given the ability to work even through a smart toaster, is not without meaning, you need to do it right: json / plain output, support for both GET and POST requests, and JSONP for every fireman.
With the help of
Poco libraries, it turned out to be implemented as a separate daemon.
- The args parameter in the GET request or the POST body must contain a JSON array.
- The optional timeout parameter is the maximum response time in ms.
- Optional parameter type - type of returned data - plain or json
- Optional jsonp or callaback parameter - function name for JSON-P request
Options 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
The code here is on
githubWhy poco?Why not boost / libevent or something? Because Poco is a very thin wrapper over system calls, without too much overweight, is collected with minimal effort and excellent documentation. And it just is much clearer to me than the boost.
How to make a good administrator and just a person who is reluctant to program?
We make a daemon that calls any other program, sends the message fields to it as arguments and interprets the output as an answer. Simplified such a CGI.
Options 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
The code here is on
githubHow to do even better?
Make access to all services through the console. With plain / hex / base64 / json input and output.
Options 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
The code here is
githubBuild everything
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
The Debian package will be in waha - *. Deb, for the rest of waha - *. Zip
Usage example
Authorization of users with a large read load and a small write.
Run broker (default port is 10000)
$ waha-broker
Run balancer for reading (port 10001 by default)
$ waha-proxy-balance -n system.user.check
Run httpasswd to check login / password (run as many times as you want to distribute the load). Displays only the exit code. Everything that comes after - is passed as arguments to the script.
$ waha-service-script -u tcp: //127.0.0.1: 10001 -n system.user.check -s htpasswd --ret-code --no-stdout - -bv users.passwd
Run the script to add users (without balancing) along with the output
$ waha-service-script -n system.user.add -s htpasswd --ret-code --stderr - -b users.passwd
Starting the HTTP interface (default port 9001)
$ waha-proxy-http
sample request
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
Using the command interface
Output without formatting:
$ waha-cli -n system.user.check admin admin
JSON output:
$ waha-cli -o json -n system.user.check admin admin
Total
Modular, working message broker, written during a vacation for recreation and entertainment by a programmer just for fun. So that the code does not disappear, put it in public access.
If anyone needs this - please consult.