Thalassa is a lightweight service registry build primarily on node.js, Redis and Axon, inspired by Seaport. Thalassa is actually a system of components primarily geared to enable continuous deployment scenarios through dynamic configuration of HAProxy load balancers and seamless, no connection drop A/B deploys.
This is the central module that includes the server registry and client. The registry uses a ping and expiration type approach. Clients register with the server and pass an optional secondsToExpire
property telling the server when it is acceptable to expire the registration if the registration is not updated in that time. There is an internal "reaper" function that runs periodically to reap expired registrations. More on that later.
Thalassa is a second generation system, superseding what was otherwise knows as "Spindrift" inside of Pearson. Spindrift leaned heavily on @substack's Seaport module. Incidentally, Seaport was the original inspiration for the aquatic theme of Spindrift and Thalassa.
In Greek mythology, Thalassa was the primeval spirit of the sea. In the fables of Aesop, Thalassa appears as a woman formed of sea water rising up from her native element. Thalassa was depicted in Roman-era mosaics as a woman half submerged in the sea, with crab-claw horns, clothed in bands of seaweed, and holding a ship's oar.1
npm install thalassa
or globally
npm install -g thalassa
The Thalassa server and client may be run from the command line or embedded as a module within your application.
Assuming Redis is installed (version >= 2.6 required due to use of EVALHSA command) and running, start the thalassa server with default options (NOTE that the default REST API port is 9000):
./node_modules/.bin/thalassa-server --debug
or
thalassa-server --debug
thalassa-server --help
Options:
--host host to bind to [default: "127.0.0.1"]
--port port to bind to for axon socket [default: 5001]
--apihost host to bind to [default: "127.0.0.1"]
--apiport port to bind to for http api [default: 9000]
--redisHost Redis host [default: "127.0.0.1"]
--redisPort Redis port [default: 6379]
--redisDatabase Redis database to select [default: 0]
--reaperFreq Reaper frequency (ms) [default: 2000]
--debug enabled debug logging
The same options above (except debug
) may be passed by properties set in the opts
object. For example using new Thalassa.Server(opts)
:
var Thalassa = require('thalassa');
var server = new Thalassa.Server({
port: 4444,
apiport: 4445,
host: 'localhost'
});
In addition opts.log
may be optionally set to your own function to handle logging. opts.log
expects this signature: function log (level, message, object){}
. level
will be one of debug
, info
, and error
. message
is a string and object
is an optional object with key value pairs. Of opts.log
is not passed, the module will be quiet.
The client can be run any of three ways.
- From the command-line
- As a module
- Over HTTP
Why would you do this? Let's say you have an existing legacy Java application that you'd rather not change. You can create a sister service that invokes the command line client to register the service on it's behalf.
For example, if Thalassa is installed globally (other wise `./node_modules/.bin/thalassa-client):
thalassa-client --register myapp@1.0.0:8080 --debug
This registers the application named my app
at version 1.0.0
that's on the current host on port 8080
. The client will continue to ping the Thalassa server with updates.
thalassa-client --help
Options:
--host thalassa host [default: "127.0.0.1"]
--port thalassa axon socket port [default: 5001]
--apiport thalassa http api port [default: 9000]
--register name@x.x.x:port,name@x.x.x:port [required]
--secsToExpire default time in seconds for a thalassa registration to be valid [default: 60]
--updateFreq time frequency in ms to ping the thalassa server [default: 20000]
--updateTimeout time in ms to wait for a registrion request to respond [default: 2500]
--debug enabled debug logging
Using the client from within a node.js application to register your service is simple. Pass options via the opts
object like new Thalassa.Client(opts)
:
var Thalassa = require('thalassa');
var client = new Thalassa.Client({
port: 4444,
apiport: 4445,
host: 'localhost'
});
client.register('myapp', '1.0.0', 8080);
// start reporting registrations to the server
client.start();
// stop reporting registrations to the server
client.stop();
opts.log
may be passed just like the server.
The client will periodically check in with the Thalassa server according to opts.updateFreq
(default 20000ms). Each registration will product a updateSuccessful
or updateFailed
event to be emitted.
client.on('updateSuccessful', function () {}); client.on('updateFailed', function (error) {});
If running as a module, you also have access to subscribe
to online
and offline
events of certain applications. For example:
client.subscribe('myapp', '1.0.0');
client.on('online', function (registration) {});
client.on('offline', function (registration) {});
Alternatively for all versions of myapp
:
client.subscribe('myapp');
Or every service registration:
client.subscribe();
Also as a module, you can use the client API to query for registrations.
client.getRegistrations('myapp', '1.0.0', function (err, registrations) {
// registrations is an Array of Registrations
}
See the HTTP API section for the Registration
structure.
You can also pass metadata with any registration as a fourth parameter. This can be any javascript object with properties. For example:
var meta = {
az: 'use1a',
size: 'm1.large',
foo: {
bar: 'baz'
}
};
client.register('myapp', '1.0.0', 8080, meta)
The Thalassa server exposes a simple HTTP API so it's not necessary to use the node.js
client and any application that's capable of calling HTTP can participate as an application in the system. See the HTTP API.
Return Registrations[]
of all registrations for the optionally provided name
and version
. /registrations
returns everything.
Registration
is defined in it's own module thalassa-registrations
. A typical registration looks like this:
{
"name": "myapp",
"version": "1.0.0",
"host": "192.168.8.106",
"port": 8080,
"lastKnown": 1378682020883,
"meta": {
"hostname": "mb-mbp.local",
"pid": 66593,
"registered": 1378682010864
},
"id": "/myapp/1.0.0/192.168.8.106/8080"
}
All times are in Unix time since epoch form.
The Thalassa client will automatically add meta.pid
and the server will automatically add registered
and hostname
if not provided. If a hostname
is not provided by the client, the IP will be used instead. Additionally, the Thalassa client will automatically set hostname
to require('os').hostname()
.
Create or update a registration.
The BODY of the POST should be application/json
and will be added to meta
.
Additionally meta.secondsToExpire
should be set to explicitly set the expiration time of the registration. In essence you are telling the Thalassa server, if you don't hear back from me in so many seconds, expire my registration and fire an offline
event. This properly allows the client to tune how often they poll balanced with how long they are willing to accept stale registration data. If you set secondsToExpire
to 300
then you may poll every ten minutes, but if your service goes down or is underplayed, consumers won't know it for at most 300
seconds.
Explicitly delete a registration, causing an offline
event.
Returns basic status with thalassa version, memory usage, uptime and registrationsPerSecond stats.
Thalassa currently doesn't implement any type of authentication or authorization and at this point expects to be running on a trusted private network. This will be addressed in the future. Ultimately auth should be extensible and customizable. Suggestions and pull requests welcome!
Licensed under Apache 2.0. See LICENSE file.