Microservices with Grenache

Microservices are awesome! But they also bring their own problems with them. How to solve service discovery? How does distribution of tasks work between them? Distributed Hashtables are an efficient solution to these problems. In Grenache, we use Kademlia. Kademlia is also used in BitTorrent for Peer-to-Peer Networking.

Today, we want to take a look at how we can use Grenache to create a microservice setup in less than 80 lines of code. Everyone knows that Node.js is bad with CPU intensive tasks, as they block our server. In our tutorial, we will create a Fibonacci number service that our other Node.js services can use.

Overview

In Grenache, a package called Grape manages our Distributed Hash Table. Grape stores the IP and port of each participant in our overlay network. Each participant can announce something under a given name. That’s handy to find remote peers on the network that offer something for us.

The Fibonacci Service

As mentioned, we are going to build a Fibonacci service. Let’s get our hands dirty and install Grape:

npm i -g grenache-grape

 
We then start two Grapes and connect them to each other:

grape --dp 20001 --aph 30001 --bn '127.0.0.1:20002'
grape --dp 20002 --aph 40001 --bn '127.0.0.1:20001'

 

Creating the Fibonacci Microservice

Within a Grenache network, connected peers can interact in different ways with each other.
They can store and retrieve data in the DHT. They can also work in a Pub/Sub pattern. Or they can work in RPC mode with different workers and clients.

In this example, we create a microservice for RPC. It registers as a RPC worker. We create a directory and install grenache-nodejs-ws. Finally, we create the file server.js:

mkdir grenache-nodejs-example-fib-server
cd grenache-nodejs-example-fib-server
npm init
npm install --save grenache-nodejs-ws
touch server.js 

We open our server.js and require Link and PeerRPCServer from Grenache:

const { Link, PeerRPCServer } = require('grenache-nodejs-ws')

Next, we add our Fibonacci functionality for demonstration purposes:

function fibonacci (num) {
  if (num <= 1) {
    return 1
  }
  
  return fibonacci(num - 1) + fibonacci(num - 2)
}

With a link, we point to our Grape setup:

const link = new Link({
  grape: 'http://127.0.0.1:30001'
})
link.start()

Our Peer can then use the link to connect to Grape:

const peer = new PeerRPCServer(link, {})
peer.init()

We create a RPC server and let it listen on port 1337:

const service = peer.transport('server')
service.listen(1337)

Next, we announce our Fibonacci worker:

setInterval(() => {
  link.announce('fibonacci_worker', service.port, {})
}, 1000)

The peers send a payload to the worker containing the desired length of our Fibonacci sequence.
When a peer sends us a request, we calculate the sequence and reply with the result:

service.on('request', (rid, key, payload, handler) => {
  const result = fibonacci(payload.number)
  handler.reply(null, result)
})

Start the server in a new terminal window with:

node server.js

 

Here you can find the full server.js:

'use strict'

const { Link, PeerRPCServer }  = require('grenache-nodejs-ws')

function fibonacci (n) {
  if (n <= 1) {
    return 1
  }

  return fibonacci(n - 1) + fibonacci(n - 2)
}

const link = new Link({
  grape: 'http://127.0.0.1:30001'
})
link.start()

const peer = new PeerRPCServer(link, {})
peer.init()

const service = peer.transport('server')
service.listen(1337)

setInterval(() => {
  link.announce('fibonacci_worker', service.port, {})
}, 1000)

service.on('request', (rid, key, payload, handler) => {
  const result = fibonacci(payload.number)
  handler.reply(null, result)
})

The Client

Let’s setup our client:

mkdir grenache-nodejs-example-fib-client
cd grenache-nodejs-example-fib-client
npm init
npm install --save grenache-nodejs-ws
touch client.js

 

In the client.js, we require the Link again. This time, we also require the PeerRPCClient:

const { Link, PeerRPCClient }  = require('grenache-nodejs-ws')

As with the server, a link is created and injected into the peer:

const link = new Link({
  grape: 'http://127.0.0.1:30001',
  requestTimeout: 10000
})
link.start()

const peer = new Peer(link, {})
peer.init()

Now we want to know the 10th number in the Fibonacci sequence. Once our link has a connection, we fire our request.

Behind the scenes, Grenache looks up which workers have registered as fibonacci_worker. It then establishes a connection with one of them.

peer.request('fibonacci_worker', { number: 10 }, { timeout: 10000 }, (err, result) => {
  if (err) throw err
  console.log(result)
})

In case we want to distribute larger tasks to a set of workers, we can use peer.map.

Here you can find the full client.js:

'use strict'

const { Link, PeerRPCClient }  = require('grenache-nodejs-ws')

const link = new Link({
  grape: 'http://127.0.0.1:30001',
  requestTimeout: 10000
})
link.start()

const peer = new PeerRPCClient(link, {})
peer.init()

const payload = { number: 10 }
peer.request('fibonacci_worker', payload, { timeout: 100000 }, (err, result) => {
  if (err) throw err
  console.log(
    'Fibonacci number at place',
    payload.number,
    'in the sequence:',
    result
  )
})

 

Make sure that Grape and the server run. Start the client in a new terminal window with:

node client.js

 

… Et voíla! We get an answer from our service.

What happened? The client peer asks Grape which clients in our network announce as fibonacci_worker. Our RPC is sent to the remote worker right after we have the address.

We hope you enjoyed this quick example. We’ve built a small peer network with microservices.

Bitfinex uses Grenache in production. There are Ruby and Node.js implementations available. We are happy to receive any feedback and contributions.

Resources:

https://github.com/bitfinexcom/grenache

https://github.com/bitfinexcom/grenache-grape

https://github.com/bitfinexcom/grenache-nodejs-ws
https://github.com/bitfinexcom/grenache-nodejs-http
https://github.com/bitfinexcom/grenache-nodejs-ws-tls

https://github.com/bitfinexcom/grenache-ruby-ws
https://github.com/bitfinexcom/grenache-ruby-http

No Comments

Post A Comment