362 lines
8.0 KiB
Markdown
362 lines
8.0 KiB
Markdown
|
# Axon
|
||
|
|
||
|
Axon is a message-oriented socket library for node.js heavily inspired by zeromq. For a light-weight
|
||
|
UDP alternative you may be interested in [punt](https://github.com/visionmedia/punt).
|
||
|
|
||
|
[![Build Status](https://travis-ci.org/unitech/pm2-axon.png)](https://travis-ci.org/unitech/pm2-axon)
|
||
|
|
||
|
## Installation
|
||
|
|
||
|
$ npm install axon
|
||
|
|
||
|
## Features
|
||
|
|
||
|
- message oriented
|
||
|
- automated reconnection
|
||
|
- light-weight wire protocol
|
||
|
- mixed-type arguments (strings, objects, buffers, etc)
|
||
|
- unix domain socket support
|
||
|
- fast (~800 mb/s ~500,000 messages/s)
|
||
|
|
||
|
## Events
|
||
|
|
||
|
- `close` when server or connection is closed
|
||
|
- `error` (err) when an un-handled socket error occurs
|
||
|
- `ignored error` (err) when an axon-handled socket error occurs, but is ignored
|
||
|
- `socket error` (err) emitted regardless of handling, for logging purposes
|
||
|
- `reconnect attempt` when a reconnection attempt is made
|
||
|
- `connect` when connected to the peer, or a peer connection is accepted
|
||
|
- `disconnect` when an accepted peer disconnects
|
||
|
- `bind` when the server is bound
|
||
|
- `drop` (msg) when a message is dropped due to the HWM
|
||
|
- `flush` (msgs) queued when messages are flushed on connection
|
||
|
|
||
|
## Patterns
|
||
|
|
||
|
- push / pull
|
||
|
- pub / sub
|
||
|
- req / rep
|
||
|
- pub-emitter / sub-emitter
|
||
|
|
||
|
## Mixed argument types
|
||
|
|
||
|
Backed by [node-amp-message](https://github.com/visionmedia/node-amp-message)
|
||
|
you may pass strings, objects, and buffers as arguments.
|
||
|
|
||
|
```js
|
||
|
push.send('image', { w: 100, h: 200 }, imageBuffer);
|
||
|
pull.on('message', function(type, size, img){});
|
||
|
```
|
||
|
|
||
|
## Push / Pull
|
||
|
|
||
|
`PushSocket`s distribute messages round-robin:
|
||
|
|
||
|
```js
|
||
|
var axon = require('axon');
|
||
|
var sock = axon.socket('push');
|
||
|
|
||
|
sock.bind(3000);
|
||
|
console.log('push server started');
|
||
|
|
||
|
setInterval(function(){
|
||
|
sock.send('hello');
|
||
|
}, 150);
|
||
|
```
|
||
|
|
||
|
Receiver of `PushSocket` messages:
|
||
|
|
||
|
```js
|
||
|
var axon = require('axon');
|
||
|
var sock = axon.socket('pull');
|
||
|
|
||
|
sock.connect(3000);
|
||
|
|
||
|
sock.on('message', function(msg){
|
||
|
console.log(msg.toString());
|
||
|
});
|
||
|
```
|
||
|
|
||
|
|
||
|
Both `PushSocket`s and `PullSocket`s may `.bind()` or `.connect()`. In the
|
||
|
following configuration the push socket is bound and pull "workers" connect
|
||
|
to it to receive work:
|
||
|
|
||
|
![push bind](http://f.cl.ly/items/473u3m1a0k1i0J0I3s04/ss-push.png)
|
||
|
|
||
|
This configuration shows the inverse, where workers connect to a "sink"
|
||
|
to push results:
|
||
|
|
||
|
![pull bind](http://f.cl.ly/items/3Y0j2v153Q0l1r373i0H/ss-pull.png)
|
||
|
|
||
|
## Pub / Sub
|
||
|
|
||
|
`PubSocket`s send messages to all subscribers without queueing. This is an
|
||
|
important difference when compared to a `PushSocket`, where the delivery of
|
||
|
messages will be queued during disconnects and sent again upon the next connection.
|
||
|
|
||
|
```js
|
||
|
var axon = require('axon');
|
||
|
var sock = axon.socket('pub');
|
||
|
|
||
|
sock.bind(3000);
|
||
|
console.log('pub server started');
|
||
|
|
||
|
setInterval(function(){
|
||
|
sock.send('hello');
|
||
|
}, 500);
|
||
|
```
|
||
|
|
||
|
`SubSocket` simply receives any messages from a `PubSocket`:
|
||
|
|
||
|
```js
|
||
|
var axon = require('axon');
|
||
|
var sock = axon.socket('sub');
|
||
|
|
||
|
sock.connect(3000);
|
||
|
|
||
|
sock.on('message', function(msg){
|
||
|
console.log(msg.toString());
|
||
|
});
|
||
|
```
|
||
|
|
||
|
`SubSocket`s may optionally `.subscribe()` to one or more "topics" (the first multipart value),
|
||
|
using string patterns or regular expressions:
|
||
|
|
||
|
```js
|
||
|
var axon = require('axon');
|
||
|
var sock = axon.socket('sub');
|
||
|
|
||
|
sock.connect(3000);
|
||
|
sock.subscribe('user:login');
|
||
|
sock.subscribe('upload:*:progress');
|
||
|
|
||
|
sock.on('message', function(topic, msg){
|
||
|
|
||
|
});
|
||
|
```
|
||
|
|
||
|
## Req / Rep
|
||
|
|
||
|
`ReqSocket` is similar to a `PushSocket` in that it round-robins messages
|
||
|
to connected `RepSocket`s, however it differs in that this communication is
|
||
|
bi-directional, every `req.send()` _must_ provide a callback which is invoked
|
||
|
when the `RepSocket` replies.
|
||
|
|
||
|
```js
|
||
|
var axon = require('axon');
|
||
|
var sock = axon.socket('req');
|
||
|
|
||
|
sock.bind(3000);
|
||
|
|
||
|
sock.send(img, function(res){
|
||
|
|
||
|
});
|
||
|
```
|
||
|
|
||
|
`RepSocket`s receive a `reply` callback that is used to respond to the request,
|
||
|
you may have several of these nodes.
|
||
|
|
||
|
```js
|
||
|
var axon = require('axon');
|
||
|
var sock = axon.socket('rep');
|
||
|
|
||
|
sock.connect(3000);
|
||
|
|
||
|
sock.on('message', function(img, reply){
|
||
|
// resize the image
|
||
|
reply(img);
|
||
|
});
|
||
|
```
|
||
|
|
||
|
Like other sockets you may provide multiple arguments or an array of arguments,
|
||
|
followed by the callbacks. For example here we provide a task name of "resize"
|
||
|
to facilitate multiple tasks over a single socket:
|
||
|
|
||
|
```js
|
||
|
var axon = require('axon');
|
||
|
var sock = axon.socket('req');
|
||
|
|
||
|
sock.bind(3000);
|
||
|
|
||
|
sock.send('resize', img, function(res){
|
||
|
|
||
|
});
|
||
|
```
|
||
|
|
||
|
Respond to the "resize" task:
|
||
|
|
||
|
```js
|
||
|
var axon = require('axon');
|
||
|
var sock = axon.socket('rep');
|
||
|
|
||
|
sock.connect(3000);
|
||
|
|
||
|
sock.on('message', function(task, img, reply){
|
||
|
switch (task) {
|
||
|
case 'resize':
|
||
|
// resize the image
|
||
|
reply(img);
|
||
|
break;
|
||
|
}
|
||
|
});
|
||
|
```
|
||
|
|
||
|
## PubEmitter / SubEmitter
|
||
|
|
||
|
`PubEmitter` and `SubEmitter` are higher-level `Pub` / `Sub` sockets, using the "json" codec to behave much like node's `EventEmitter`. When a `SubEmitter`'s `.on()` method is invoked, the event name is `.subscribe()`d for you. Each wildcard (`*`) or regexp capture group is passed to the callback along with regular message arguments.
|
||
|
|
||
|
app.js:
|
||
|
|
||
|
```js
|
||
|
var axon = require('axon');
|
||
|
var sock = axon.socket('pub-emitter');
|
||
|
|
||
|
sock.connect(3000);
|
||
|
|
||
|
setInterval(function(){
|
||
|
sock.emit('login', { name: 'tobi' });
|
||
|
}, 500);
|
||
|
```
|
||
|
|
||
|
logger.js:
|
||
|
|
||
|
```js
|
||
|
var axon = require('axon');
|
||
|
var sock = axon.socket('sub-emitter');
|
||
|
|
||
|
sock.bind(3000);
|
||
|
|
||
|
sock.on('user:login', function(user){
|
||
|
console.log('%s signed in', user.name);
|
||
|
});
|
||
|
|
||
|
sock.on('user:*', function(action, user){
|
||
|
console.log('%s %s', user.name, action);
|
||
|
});
|
||
|
|
||
|
sock.on('*', function(event){
|
||
|
console.log(arguments);
|
||
|
});
|
||
|
```
|
||
|
|
||
|
## Socket Options
|
||
|
|
||
|
Every socket has associated options that can be configured via `get/set`.
|
||
|
|
||
|
- `identity` - the "name" of the socket that uniqued identifies it.
|
||
|
- `retry timeout` - connection retry timeout in milliseconds [100] (0 = do not reconnect)
|
||
|
- `retry max timeout` - the cap for retry timeout length in milliseconds [5000]
|
||
|
- `hwm` - the high water mark threshold for queues [Infinity]
|
||
|
|
||
|
## Binding / Connecting
|
||
|
|
||
|
In addition to passing a portno, binding to INADDR_ANY by default, you
|
||
|
may also specify the hostname via `.bind(port, host)`, another alternative
|
||
|
is to specify the url much like zmq via `tcp://<hostname>:<portno>`, thus
|
||
|
the following are equivalent:
|
||
|
|
||
|
```
|
||
|
sock.bind(3000)
|
||
|
sock.bind(3000, '0.0.0.0')
|
||
|
sock.bind('tcp://0.0.0.0:3000')
|
||
|
|
||
|
sock.connect(3000)
|
||
|
sock.connect(3000, '0.0.0.0')
|
||
|
sock.connect('tcp://0.0.0.0:3000')
|
||
|
```
|
||
|
|
||
|
You may also use unix domain sockets:
|
||
|
|
||
|
```
|
||
|
sock.bind('unix:///some/path')
|
||
|
sock.connect('unix:///some/path')
|
||
|
```
|
||
|
|
||
|
## Protocol
|
||
|
|
||
|
Axon 2.x uses the extremely simple [AMP](https://github.com/visionmedia/node-amp) protocol to send messages on the wire. Codecs are no longer required as they were in Axon 1.x.
|
||
|
|
||
|
## Performance
|
||
|
|
||
|
Preliminary benchmarks on my Macbook Pro based on 10 messages
|
||
|
per tick as a realistic production application would likely have
|
||
|
even less than this. "better" numbers may be acheived with batching
|
||
|
and a larger messages/tick count however this is not realistic.
|
||
|
|
||
|
64 byte messages:
|
||
|
|
||
|
```
|
||
|
|
||
|
min: 47,169 ops/s
|
||
|
mean: 465,127 ops/s
|
||
|
median: 500,000 ops/s
|
||
|
total: 2,325,636 ops in 5s
|
||
|
through: 28.39 mb/s
|
||
|
|
||
|
```
|
||
|
|
||
|
1k messages:
|
||
|
|
||
|
```
|
||
|
|
||
|
min: 48,076 ops/s
|
||
|
mean: 120,253 ops/s
|
||
|
median: 121,951 ops/s
|
||
|
total: 601,386 ops in 5.001s
|
||
|
through: 117.43 mb/s
|
||
|
|
||
|
```
|
||
|
|
||
|
8k messages:
|
||
|
|
||
|
```
|
||
|
|
||
|
min: 36,496 ops/s
|
||
|
mean: 53,194 ops/s
|
||
|
median: 50,505 ops/s
|
||
|
total: 266,506 ops in 5.01s
|
||
|
through: 405.84 mb/s
|
||
|
|
||
|
````
|
||
|
|
||
|
32k messages:
|
||
|
|
||
|
```
|
||
|
|
||
|
min: 12,077 ops/s
|
||
|
mean: 14,792 ops/s
|
||
|
median: 16,233 ops/s
|
||
|
total: 74,186 ops in 5.015s
|
||
|
through: 462.28 mb/s
|
||
|
|
||
|
```
|
||
|
|
||
|
## What's it good for?
|
||
|
|
||
|
Axon are not meant to combat zeromq nor provide feature parity,
|
||
|
but provide a nice solution when you don't need the insane
|
||
|
nanosecond latency or language interoperability that zeromq provides
|
||
|
as axon do not rely on any third-party compiled libraries.
|
||
|
|
||
|
## Running tests
|
||
|
|
||
|
```
|
||
|
$ npm install
|
||
|
$ make test
|
||
|
```
|
||
|
|
||
|
## Authors
|
||
|
|
||
|
- [visionmedia](http://github.com/visionmedia)
|
||
|
- [gjohnson](https://github.com/gjohnson)
|
||
|
|
||
|
## Links
|
||
|
|
||
|
- [Screencast](https://vimeo.com/45818408)
|
||
|
- [Axon RPC](https://github.com/visionmedia/axon-rpc)
|
||
|
|
||
|
## License
|
||
|
|
||
|
MIT
|