meteorhacks/cluster

Name: cluster

Owner: meteorhacks

Description: Clustering solution for Meteor with load balancing and service discovery

Created: 2015-01-19 12:10:15.0

Updated: 2018-03-21 19:00:35.0

Pushed: 2017-01-23 13:05:42.0

Homepage: https://meteorhacks.com/cluster-a-different-kind-of-load-balancer-for-meteor.html

Size: 666

Language: JavaScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Cluster Build Status

Clustering solution for Meteor with load balancing and service discovery.

TLDR; With cluster, we can scale Meteor apps by simply installing a Meteor package. No need to use tools like Nginx or HaProxy. It's built for Meteor and you don't need to worry about configuring IP addresses and so on. Just add more instances and let cluster take care of load balancing.

Cluster also has first class support for MicroServices.

Table of Contents

Concept

When we need to scale Meteor, it's about scaling DDP requests since that's where the traffic flows. When we need to do horizontal scaling, we generally put a load balancer like Ngnix or HaProxy in front of Meteor. Then, it'll do the load balancing.

Okay, what happens when we need to add another instance? We'd need to reconfigure the load balancer again. That will reset all the existing DDP connections.

Then what if the load balancer goes down? To fix that, we'd need to add a load balancer working parentally. That makes the setup more complex.


Cluster has an interesting way to solve this problem. It turns your Meteor app into a load balancer and you don't need to use a separate tool for that. When you add a new server (or an instance), you wouldn't even need to reconfigure cluster. Cluster will simply detect new instances and route traffic to them.

Any of the instances within the cluster can act as a load balancer. So, even if one server goes down, you don't need to worry much. Also, it's configured specially for Meteor.

Cluster can do these things because it acts as a service discovery solution. Currently, that is implemented using MongoDB, but we can have more implementations later.

Since cluster is a service discovery solution, it's perfect for MicroServices. Read to the end :)

Getting Started

Simply add cluster into your app.

or add meteorhacks:cluster

Then when you are deploying or starting your app, export the following environment variables.

u can use your existing MONGO_URL for this
rt CLUSTER_DISCOVERY_URL=mongodb://host:port/db,
is is the direct URL to your server (it could be a private URL)
rt CLUSTER_ENDPOINT_URL=http://ipaddress
rk your server as a web service (you can set any name for this)
rt CLUSTER_SERVICE=web

Now start as many servers as you like and DDP traffic will be sent to each of the instances randomly. You can also remove instances anytime without affecting the cluster or your app.

Live Demo - How to use cluster to scale your app

API

We have a very simple API and there are two version of the API:

  1. JavaScript API
  2. Environment Variables based API

For a production app, it's recommended to use the Environment Variables based API.

JS API
onnect to the cluster with a MongoDB URL. Better if it's a replica set
connectOptions = {
 Value of 0 to 1, mentioning which portion of requestes to process here or proxy
 If 1, all the requests allocated to this host will get processed
 If 0.5 half of the requsted allocated to this host will get processed, others will get proxied
 If 0, only do proxying 
lfWeight: 1 // optional


ter.connect("mongodb://mongo-url", connectOptions)

egister a service to the cluster
options = {
dpoint: "a direct url to the instance",
lancer: "balancer URL, if this is a balancer" // optional
Service: "service to proxy UI" // (optional) read to the end for more info


ter.register("serviceName", options);

xpose services to the public
ter.allowPublicAccess(["service1", "service2"]);

iscover a DDP connection
 This is available on both the client and the server
ter.discoverConnection("serviceName");
Environment Variables based API
nnect to the cluster with a MongoDB URL. Better if it's a replica set
rt CLUSTER_DISCOVERY_URL=mongodb://mongo-url

gister a service to the cluster
rt CLUSTER_ENDPOINT_URL="a direct url to the instance"
rt CLUSTER_SERVICE="serviceName"

rt CLUSTER_BALANCER_URL="balancer URL, if this is a balancer" #optional
rt CLUSTER_UI_SERVICE="ui-service-name" #optional - read to the end for more info

pose services to the public
rt CLUSTER_PUBLIC_SERVICES="service1, service2"

eck JS API's connectOptions.selfWeight for docs
rt CLUSTER_SELF_WEIGHT="0.6"
Multi-Core Support

Cluster has the multi-core support as well. You can run your Meteor app utilizing all the cores in your server as follows:

Make sure you've added the meteorhacks:cluster package.

Then expose following environment variable:

rt CLUSTER_WORKERS_COUNT=auto

That?s all you have to do. Now your Meteor app will use all the cores available on your server.

You can also specify the number of workers explicitly like this:

rt CLUSTER_WORKERS_COUNT=2

For more information follow this article.

If you are using Meteor Up to deploy your app, make sure you've the latest version.

MicroServices

With Microservices, we build apps as a set of tiny services rather than create a monolithic app. These services can be deployed independently.

Cluster is a tool which has built-in support for Microservices. With cluster, you can manage a set of Microservices very easily. Cluster has many features that are perfect for Microservices:

A Simple app based on Microservices

For example, if we want to build our own version of Atmosphere to search Meteor packages and we decided to build it with Microservices. In this case, we'd have two such services:

Each of these services is it's own Meteor app.

Service Registration & Discovery

First we need a Mongo URL for the cluster. That's how cluster communicates with each node. It's better if you can create a separate MongoDB ReplicaSet for that. It doesn't need to have oplog support.

Next, we add the following configuration to the search service inside server/app.js.

ter.connect("mongodb://mongo-url");
ter.register("search");

eteor methods
or.methods({
earchPackages": function(searchText) {
return ["a list of packages"];


Then we add the following configuration to server/app.js to the web service.

ter.connect("mongodb://mongo-url");
ter.register("web");
ter.allowPublicAccess("search");

searchConn = Cluster.discoverConnection("search");
packagesFromMeteorHacks = searchConn.call("searchPackages", "meteorhacks");
ole.log("here is list of", packagesFromMeteorHacks);

In the above example, you can see how we made a connection to the “search” service from the “web” service.

We've also allowed clients to connect directly to the search service from the browser (or from cordova apps). That was done with a single line:

ter.allowPublicAccess("search");

This way, you can access the “search” service from the client side of the “web” service as shown below:

searchConn = Cluster.discoverConnection("search");
chConn.call("searchPackages", "meteorhacks", function(err, packages) {
(err) throw err;
nsole.log("here is list of", packages);

Learn More about Microservices

If you'd like to learn more, here are a few resources for you.

Multiple Balancers

The setup we discussed earlier in the getting-started section still has one issue. All the DDP connections are routing through a single instance which is normally the server you pointed your domain name to via DNS.

But that's not ideal. If it goes down you lose access to the whole cluster. Additionally, you will likely be facing scaling issues as well since all traffic is routing through that single server.

Cluster has a built in solution for that. It's called Balancers. A Balancer is an instance of your cluster which acts as a load balancer. You can add or remove them as needed.

Making your instance a Balancer is pretty simple. Just export the following environment variable.

rt CLUSTER_BALANCER_URL=https://subdomain.domainname.com

This URL is open to public and it should point to this instance. Now configure your instances to run as Balancers and your cluster will start to load balance DDP connections through them.

Demo & Presentation - Learn more about Balancers

UI Service

In cluster, there is a special concept called “UI Service”. By default, if you visit a service, it's UI will be served to you. For an example, let's say we've two services:

So, if you visit the web app on port 7000, you'll get the UI of the web app. If you visit the search app on port 8000, you'll get the UI of the search app.

But it's possible, search apps to give the UI of the “web” app as well. With that we can make sure, all the services in the cluster exposes the same UI. For that, simply expose following environment variable.

rt CLUSTER_UI_SERVICE="web"
Practical Setup

Let's see how you could setup Cluster in a practical scenario. BulletProof Meteor is already running Cluster so I will show you an excerpt of its setup. (I've changed some information for the educational purposes)

We have four Servers and three of them are Balancers. This is how they are structured.


p-1": {
"endpointUrl": "http://ip-1",
"balancerUrl": "https://one.bulletproofmeteor.com"

p-2": {
"endpointUrl": "http://ip-2",
"balancerUrl": "https://two.bulletproofmeteor.com"

p-3": {
"endpointUrl": "http://ip-3",
"balancerUrl": "https://three.bulletproofmeteor.com"

p-4": {
"endpointUrl": "http://ip-4"


I'm using Meteor Up to deploy and here's a sample configuration file. With Meteor Up, you don't need to expose CLUSTER_ENDPOINT_URL. It'll automatically do that by itself.

Make sure you install the latest version of Meteor Up.

DNS & SSL

We'll use cloudflare for DNS and SSL setup.

We turn off WebSockets since cloudflare does not support SSL with WebSockets yet!

As this setup, ip-1 and ip-2 will take care of load balancing for static content while ip-1, ip-2 and ip-3 is take care of load balancing DDP connections.

All 4 servers process DDP and provide Static Content.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.