Node.js clustered app with autodiscovery. Part 2 – the demo app.

If you have missed – here is the Part 1 of the article, the one where I am researching for the tools we do need to be able to build the Node.js clustered app with autodiscovery.

Let’s continue, then. First of all – you can find the application on github here: https://github.com/PavelPolyakov/nodejs-clustered-app-with-autodiscovery . There you can also find the setup guide and try to launch the cluster yourself, you don’t need much, just four Vagrant machines.

In the application below is a demo example of the Node.js clustered app with autodiscovery. I’m sure it’s possible to build another setups and, probably, there are some already, but I came up with the next one.

Setup structure, Vagrant

Here is the structure of the setup.

Node.js app structure

My application consists from two major parts:

  1. etcd server. The server which is our key-value storage, the nodes would register theirselves there, the other nodes would read the list of the registered nodes and this way knew which nodes are already in cluster.|
    It’s important to state that in this setup the etcd is launched as single node, not in the cluster, this is done only because it’s demo, you shouldn’t do this in production.
    etcd server is run using Vagrant, it’s installed according to the instructions from the provision.sh file. Besides the server, we install the etcd-browser on the machine, so it’s possible to track the keys visually.
  2. node-app. The node app is an example of the Node.js clustered app. The main goal of the app is:
    1. start
    2. register itself in the etcd server
    3. find out if there are any nodes in the cluster
    4. join cluster
    5. do some job

    In this particular app the master node is going to send the tasks to the worker nodes. Worked nodes would do the job and return the results.
    It’s possible to launch several Vagrant machines from one Vagrantfile, so we will launch three machines – A, B and C, which will luckily form the cluster.
    Besides, there is one important configuration option for the node-app – the IP of the etcd cluster server, it should be filled in to the config/default.js file, like this:

The app

I need to say, that for better understanding it would be better if you checkout the codebase from github, follow the setup instructions and run it. But I don’t think you will do that at this very moment – so continue reading 🙂

Here is the require block:

Things to mention are the next:

  1. moniker library – allows us to get heroku like hostnames
  2. _getIP method – allows us to get our real external IP, filtering the network interfaces, which are provided by the Node.js os module.
  3. node-discover library – which allows us to form the cluster, is responsible for master election and allows us to send the messages through cluster.
  4. node-etcd library – nodejs implementation of the etcd API, allows us to interact with the etcd server.

Here is the part when the app starts:

When we start the app, the first thing we do is the connection to the etcd server. There we register ourselves by the key services/${IP} with the ttl 120 seconds and setup the interval, which continues to register the node each 60 seconds. This is done with the purpose, if some node would crush or fail, it would be removed from cluster in 120 seconds. If everything is ok, it would continuously re-register itself. At the end – we subscribe the node to all of the changes inside the directory services, so each time the node is registered there or removed from the directory – we would be notified.

Next – we have a closer look at the watcher callback:

Each time the directory is changed, we first read all of the keys from it. Then we compare it with the array of the keys we already have (or don’t have) and decide wether it’s first time the node is launched or the change was done during the regular lifecycle.
If that was initial launch – we call the init method of the node app, if the callback was called during regular lifecycle – we call the change method. As you already guessed – the appNode is an entity, which represents the logic of our app.

The appNode looks the next way:

It consists from:

  1. id  – the unique id of the node
  2. d – reserved to hold the node-discover instance
  3. intervals – the object to hold the possible intervals
  4. init – method which is called when the node was just started
  5. change – method which is called when the list of the nodes in the cluster was updated

Now, let’s do a close look to the init method:

What happens there, finally we do call the Discovery constructor and pass there the list of the IPs which we took from the etcd server. Then, we subscribe to several events, all of these are provided by the node-discover library:

  1. promotion – called when the node become master
  2. demotion – called when the node is not the master anymore, the new was elected
  3. added – called when any new node is joining the cluster
  4. removed – called when any node is leaving the cluster

Besides, we do join two channels – tasks and results. To join a channel in terminology of node-discover, means to subscribe for the messages which goes to the channel. You do not need to join the channel, if need to send the message to it.

Now, let’s see what the master does:

In this app, the master is the node which cames up with the task and sends it to the tasks channel, the other nodes are considered as workers – the listen for the tasks channel, pick the tasks from there and do them.
The first thing the master does – it leaves the tasks channel, because it’s important to send the tasks there, not to listen for the tasks. Then, we set the interval – each 10 seconds the node generates the tasks, which should be executed by executor. Executor is defined by the load balancer – the balance method. In our example – we put the random number from 30 to 60 to the task object. The goal of the worked node – to multiply it for 3 and return the result.

Let’s see what the worker node does, then:

This callback is called each time the new message happens to the tasks channel. The node checks then – if it’s the master, if yes (it theory, it shouldn’t happen) – we should not take it. If task was not meant for us – do not take it. But if we are the executor – let’s wait from 1 to 15 seconds, and then put the result object to the channel results. The job is done then.

OK, that was it! I think I have explained everything, the only thing is left – the demo.

Below is the video which shows the app in action:

 

In this article, I have described my findings regarding the topic Node.js clustered app with autodiscovery. I can’t say that this is the best way to build the clustered app, however, I think this knowledge would be a good starting point for those who is interested.