Build highly scalable applications with Node.js and Redis

One of the most compelling reasons to use IBM Bluemix™ to run your application is its ability to quickly and easily scale your application. With traditional IaaS offerings, scaling your application would require purchasing additional virtual images, configuring those images, deploying your application, and configuring some type of load balancer to distribute the load across the new images. With Bluemix and its catalog packed with services, all this can be accomplished with a single click of a button.

Share:

Ryan Baxter, Developer Advocate, IBM Bluemix , IBM

Ryan BaxterRyan Baxter is a developer advocate for Codename: BlueMix. You can find him on Twitter at @ryanjbaxter.



27 June 2014 (First published 23 May 2014)

Also available in Chinese Russian Japanese

One of the most compelling reasons to use Bluemix to run your application is its ability to quickly and easily scale your application. With traditional Infrastructure as a Service (IaaS) offerings, scaling your application would require purchasing additional virtual images, configuring those images, deploying your application on the images, and configuring some type of load balancer to distribute the load across the new images. With Bluemix and its catalog packed with services, all this can be accomplished with a single click of a button.

WATCH:Webcast: Building Highly Scalable Applications for Bluemix

While scaling is super easy to use in Bluemix, it does not mean every application will work properly when scaled. Often, applications that run on-premise will store state in memory or within the local file system. These types of applications will often fail when scaled in the cloud because client requests will be dispatched at random to different instances of the application running in the cloud. The application state on one instance will not be the same as any other application instance. To address this, Platform as a Service (PaaS) offerings such as Bluemix provide services that applications can use to share state across multiple instances.

I will show how to build a chat application that allows users to send messages in real time to other users, scaling the application across multiple instances to handle the load.

Getting started

To build this application, you will need the following things.

  1. A basic familiarity with HTML, JavaScript, CSS, and Node.js
  2. Node.js and NPM installed. NPM will be installed with Node.js.

Creating the project

Go to a directory on your local file system where you would like to do your work and create a new folder called bluechatter.

$ mkdir bluechatter

App.js

In the bluechatter directory, create a file called app.js. Within app.js, paste the following code to create a basic web server with the popular Node.js library, Express JS.

var express = require("express");
var fs = require('fs');
var http = require('http');
var path = require('path');

var app = express();
app.set('port', 3000);
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());

// Serve up our static resources
app.get('/', function(req, res) {
  fs.readFile('./public/index.html', function(err, data) {
    res.end(data);
  });
});

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

Node.js applications use a file called package.json to describe the basic metadata about the application, as well as provide a list of dependencies. Within the bluechatter folder, create a file called package.json and paste the following code.

{
  "name": "BlueChatter",
  "version": "0.0.1",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "3.4.8"
  }
}

This gives your application a name, version, and start script. It also specifies the only dependency the application has so far: Express. Later, you will add dependencies to the application.

index.html

In the bluechatter directory, create a folder called public. Within the public directory, create a file called index.html and paste the following code into it.

<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>BlueChatter</title>

    <!-- Bootstrap -->
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
    <link href="stylesheets/style.css" rel="stylesheet">

    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
      <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>
  <body>
    <div id="main-container">
      <h1>BlueChatter</h1>
      <div class="user-form center">
        <h3 class="form-signin-heading">Enter a username to get started</h3>
        <input id="user-name" class="form-control" placeholder="Username" required="" autofocus="">
        <button class="btn btn-lg btn-primary btn-block go-user" type="submit">Go!</button>
      </div>
    </div>
    <div class="footer center">
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
    <script src="javascripts/client.js"></script>
  </body>
</html>

As you can see from this HTML, we are using Bootstrap and jQuery, both of which are being retrieved from a content delivery network (CDN). In addition, there are references to style.css and client.js. These are files we will use to add custom styles and business logic to our app.

First, let's create style.css.

style.css

In the public folder, create a folder called stylesheets. Within the stylesheets folder, create a file called style.css and paste the following CSS code into it.

#main-container {
  margin: 0 auto;
}

#main-container h1 {
  text-align: center;
}

.center {
  text-align: center;
  max-width: 430px;
  padding: 15px;
  margin: 0 auto;
}

.go-user {
  margin-top: 10px;
}

.chat-box {
  max-width: 930px;
  padding: 15px;
  margin: 0 auto;
}

.message-area {
  max-height: 500px;
  overflow: auto;
}

.footer {
  padding-top: 19px;
  color: #777;
  border-top: 1px solid #e5e5e5;
}

Now, let's create client.js.

client.js

In the public folder, create a folder called javascripts. Within the javascripts folder, create a file called client.js. Add the code below to it and save the file; you will fill in the rest of the code later.

 $(document).ready(function() {

 });

Testing the app

At this point, you have the basic server- and client-side code in place to test out the app. Before you run the app, you must run the npm install command within the bluechatter directory to install all the necessary dependencies. Open a terminal window and execute the following commands.

$ cd bluechatter
$ npm install

When running npm install, you must have an active internet connection because npm will download the needed code dependencies. After npm finishes installing the dependencies, you can launch the server. Run the following command from within the bluechatter directory.

$ node app.js

If everything is working properly, you will see "Express server listening on port 3000" displayed to your terminal window. Open your favorite browser and navigate to http://localhost:3000. In your browser, you will see something that looks similar to this.

Image shows sample browser result

Client-side code

Making the Go! button go

At this point, if you click the Go! button in the app, nothing will happen. That is because we have not yet defined its click handler.

Open the public/javascripts/client.js from the bluechatter directory in your favorite text editor. Lets add a function within the document ready callback as follows:

  var name = '';
  function go() {
    name = $('#user-name').val();
    $('#user-name').val('');
    $('.user-form').hide();
    $('.chat-box').show();
  };

This piece of code pretty straight-forward. All we are really doing is manipulating the DOM. We are getting the name the user entered in the username field of the UI and saving it in a variable because we need this later. Next, we hide the form and button, and show the chat box where the user can see and send chat messages. (The chat box is not yet defined in our HTML; we will get to that in a little bit.) We still need to call this function from the button click listener. To do that, add this code inside the document ready callback.

  $('#user-name').keydown(function(e) {
    if(e.keyCode == 13){ //Enter pressed
      go();
    }
  });
  $('.go-user').on('click', function(e) {
    go();
  });

This code not only adds a click listener to the Go! button but also adds a key listener to the username field, so if the user presses enter in the field, it will do the same thing as clicking Go!.

Finally, we need to add some HTML to view and send chat messages. Open the index.html file within the bluechatter/public folder. Find the div with the ID of main-container and add the following snippet of HTML within the main-container div after the content already there.

      <div class="chat-box well well-lg" style="display: none;">
        <div class="jumbotron">
          <h1>It Is Quiet In Here!</h1>
          <p>No one has said anything yet, type something insightful in the 
text box below and press enter!</p>
        </div>
        <div class="message-area">
        </div>
      </div>
      <div class="chat-box" style="display: none;">
        <textarea id="message-input" placeholder="Type something insightful!" 
class="form-control" rows="3"></textarea>
      </div>

Now if you still have your node server running, stop it by pressing Ctrl+c and run the following command to start your server again.

node app.js

If you go back to http://localhost:3000, the Go! button should actually work. Enter a username and click Go!. You should see this.

Figure 1. Sending chat messages
Image shows sending chat messages

If you were to try to "Type something insightful!" in the text area and press Enter, like the UI tells you to, you would again find out that nothing happens. We need to define what should happen when Enter is pressed. What should happen? Well we need to tell the server two things when a message is sent: the user sending the message and the message being sent.

To tell the server this information, we will call a REST API. The REST API is not yet defined in the server code. So for now, we will assume the REST API endpoint is /msg and accepts JSON. Let's add a key listener to the text area that will call our fictional REST API.

  $('.chat-box textarea').keydown(function(e) {
    if(e.keyCode == 13){
      $.ajax({
        type: "POST",
        url: "/msg",
        data: JSON.stringify({"username" : name, "message" : $('#message-input').val().trim()}),
        contentType: "application/json"
      });
      $(this).val('');
      $('.jumbotron').hide();
      e.preventDefault()
    }
  });

You can see this code adds a key-down listener to the text area. When the Enter key (key code 13) is pressed, we will use the jQuery Ajax API to make a POST request to /msg. The data we send in the Ajax request is a JSON object containing the username and message. We also do a little house cleaning at the end of the listener. We clear the text area, hide the UI that says "it is quiet in here" (because it is no longer quiet), then prevent the event from bubbling up by calling preventDefault.

Let's test it out by running the node app.js command and going to http://localhost:3000. Enter a username, type a message and press Enter. Looks like nothing happened, right? Well, not exactly. Open the developer tools for the browser you are in (such as Firebug in Firefox) and go to the console tab. Now type another message and press Enter. In the console, you should see a request being made to the /msg endpoint. Here is an example of what this looks like in Firebug inside of Firefox.

Image shows Firefox Firebug example

However, the request fails because our Node server does not define /msg. We will take care of that later because we still have one more thing to do in our client-side JavaScript.

Let's take a poll

The last piece of the puzzle on the client-side code is the code that receives chat messages from other users. To create this code, we take advantage of a technique called long polling. Long polling does exactly what it sounds like it does: It polls the server, and sometimes the response to the poll will take a long time. In the case of our application, the client will make a request to the server, and the server will wait to respond to that request until it has some data to send back to the client. As soon as the server responds, the client immediately sends another poll request back to the server so it can get the next chat message that comes in. To do this, we will make a GET request to a REST API on the server. The server will respond with a JSON object containing the username and the chat message sent by that user.

Add the following code snippet to client.js within the document ready callback.

  function poll() {
    $.getJSON('/poll/' + new Date().getTime(), function(response, statusText, jqXHR) {
      if(jqXHR.status == 200) {
        $('.jumbotron').hide();
        msg = response;
        var html = '<div class="panel \
panel-success"><div class="panel-heading"><h3 class="panel-title">' +
msg.username + \
'</h3></div><div class="panel-body">' + msg.message + '</div></div>';
        var d = $('.message-area');
        d.append(html);
        d.scrollTop(d.prop("scrollHeight"));
      }
      poll();
    });
  };

Again, we are using jQuery to help us make the REST API call. The getJSON API makes a request to /poll. You will notice that we also append the current time. We do this because browsers will queue requests made to the same endpoint; we don't want this, so we make the endpoint appear unique by appending the current time. In the callback, we first check to make sure the response is a 200, indicating it was a successful request. We again hide the "jumbotron" quiet message (since it is no longer quiet) and append some HTML to the chat area with the username and message. Finally, we scroll the chat area to the bottom so the new message is always visible. You will also notice that the function is recursive in that it is calling itself. This is the poll in the long-polling pattern.

The observant developer will notice that we have nothing calling our poll function to kick off the polling. When do we want to start polling? As soon as the user presses the Go! button. We already have a function that handles the Go! button click, so let's call our poll function from that listener. Add a poll function call to the end of the go function.

  function go() {
    name = $('#user-name').val();
    $('#user-name').val('');
    $('.user-form').hide();
    $('.chat-box').show();
    poll();
  };

It's time to test our app again. Run node app.js in your terminal. In your browser, make sure you have your developer console open and go to http://localhost:3000. Enter a username and click Go!. You should see our poll request being made — and failing, as shown below.

Image shows poll request failing

Again, we have not implemented the REST API on the server, so this is expected.


Server-side code

Now that our client-side code is working as we expect, we need to start building out the REST APIs on the server that the client uses.

The poll results are in

Let's first implement our poll endpoint. As mentioned, our application is using long polling, so our polling endpoint is going to handle the polling requests from the clients and respond with new chat messages as they are sent to the server.

Open app.js and add the following code.

var clients = [];
// Poll endpoint
app.get('/poll/*', function(req, res) {
  clients.push(res);
});

There is not much happening here. All we are doing is handling the request at the /poll endpoint, taking the response, and putting it in an array object. This is where the long part of long polling comes in. We will wait to respond to the poll request until we have a message to respond with.

Send me your messages

Now we just need to handle the messages sent to the server and respond to any clients waiting to receive them. Add the following code to app.js to handle the /msg endpoint.

// Msg endpoint
app.post('/msg', function(req, res) {
  message = req.body;
  var msg = JSON.stringify(message);
  while(clients.length > 0) {
    var client = clients.pop();
    client.end(msg);
  }
  res.end();
});

Our /msg endpoint receives the message contained within the POST body and then goes through all the poll requests in the client array, responding to them with the messages sent from the client. No magic here.

Testing the server-side code

We now have functioning client and server-side code, so let's test it out. Back in your terminal, run node app.js to start our server and open http://localhost:3000 in your favorite browser. We really need to two browsers in order to test our application, so open your second-favorite browser as well and go to the same URL. Enter a different username in each browser window and start typing messages. You should see the messages showing up in both browser windows. Nice! We have a functioning chat app.

Image shows chat app

Taking it to the cloud

Our application appears to working fine locally, so let's deploy it to the cloud. We can easily do this using Bluemix. But first, we need to make a small change to our server-side code.

Using the right port

Right now, we are starting our Express server on port 3000. It is hard-coded in app.js: app.set('port', 3000);.

This is fine when running locally, but when running the application on Bluemix, we need to use the port opened by the Bluemix platform, which most likely won't be 3000. Bluemix lets the application know which port to use by setting the port in an environment variable VCAP_APP_PORT. However, there is a Node library we can use that will get us the port: cf-env. Let's add this library to our package.json file so we can use it within our server code.

Open package.json, and in the dependencies object, add a property for cf-env.

  "dependencies": {
    "express": "3.4.8",
    "cf-env": "*"
  }

Now open app.js and right after the other require calls, add a require for cf-env and your package.json file.

var cfEnv = require("cf-env");
var pkg   = require("./package.json");

We just need to instantiate the cf-env library by passing a name to its getCode method, using the name in our package.json file.

var cfCore = cfEnv.getCore({name: pkg.name});

Finally, we need to change the line of code that is setting the port Express is using. Find the line of code in app.js that looks like app.set('port', 3000); and change it to be app.set('port', cfCore.port || 3000);.

Notice this code lets us run the application locally as well as in the cloud. If cfCore.port is undefined, we will just use 3000 like we did before.


Push the code to Bluemix

In order to use Bluemix, you must have an account. Head to Bluemix to register. Now that you have an account, we can install the Cloud Foundry Command Line Interface (CLI), which we will use to deploy our application. To install the CLI, follow the instructions in the Bluemix documentation.

Done? Now on to the fun stuff.

First, we need to point our CLI at Bluemix and log in. Run the following command in your terminal window.

When you are prompted to enter your username and password, enter your IBM username and password. Now that we have successfully authenticated, we will push our application to the cloud. Make sure you are within the root of the bluechatter folder containing all your application code and execute the following command in your terminal window, replacing the bluechatter portion of the command with your own app name, like my-bluechatter.

cf push bluechatter -m 128M

Tip: If you see an error when pushing your application that says "Bluemix could not create a route for your application," the name you have chosen is already in use. Pick a different name and run the command again.

If the push was successful, the CLI will print out the URL where your application is running. Open this URL in your two favorite browsers and make sure your application works as expected.

App started

Showing health and status for app bluechatter in org rjbaxter@us.ibm.com / 
space dev as rjbaxter@us.ibm.com...
OK

requested state: started
instances: 5/5
usage: 128M x 5 instances
urls: bluechatter.mybluemix.net

Optional: Excluding unnecessary files from your deployment

You may have noticed that when you executed your cf push command that the upload was fairly large (4.2 MB in my case). While 4.2 MB isn't outrageous, it is large compared to the amount of code we have written. To understand why we uploaded 4.2 MB of files, we need to understand how cf push works. The cf push command will deploy every file within the directory it is executed. If you remember back to when we first ran our application locally, we ran npm install to install all the dependencies we needed to run our app. The dependencies were installed to a folder called node_modules within the bluechatter folder. Since cf push deploys everything, it is also deploying out dependencies. You might be saying, "But we need these don't we?" Yes, we do, but Bluemix will take care of installing these for us because it will run npm install as part of the deployment.

The solution is simple: We just need to create a file in our bluechatter directory to tell the cf push command which files and directories not to upload. Create a file called .cfignore in the bluechatter directory and open it in your favorite text editor. In the .cfignore file, add the following line and save the file.

node_modules

Here we are telling the cf push command to ignore everything in the node_modules directory. In other words, don't push all our dependencies. Now run the following code.

cf push bluechatter -m 128M

You should notice a significant difference in the size of the content deployed. In my case , the cf push command only uploaded 11.4K, a much smaller — and more importantly, a much quicker — deployment.

Warning: Potential gotcha!

If you try to run node app.js locally now, you might run into the error below.

$ node app.js

module.js:340
    throw err;
          ^
Error: Cannot find module 'cf-env'
    at Function.Module._resolveFilename (module.js:338:15)
    at Function.Module._load (module.js:280:25)
    at Module.require (module.js:364:17)
    at require (module.js:380:17)
    at Object.<anonymous> (/Users/ryanjbaxter/temp/bluechatter/app.js:5:13)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)

This is because we added a dependency to our package.json file, but have not yet installed this dependency. To solve this, just run npm install from within the bluechatter directory, then run node app.js. You will need to run npm install every time you make a change to the dependencies section of package.json.

Scaling fail

An advantage of using a PaaS like Bluemix is the ability to easily scale your application horizontally. What do we mean by scale horizontally? In essence, this means to create multiple servers all running your application and all handling requests from incoming users. You can read more at Wikipedia.

Because we are sure our BlueChatter application is going to be hit with all the kids, let's make sure our application can scale in the cloud. We can use the trusty CLI to scale our application from our terminal window; just run the following command to scale the BlueChatter application up to five instances.

cf scale bluechatter -i 5

In a matter of seconds, the command should return saying everything went OK. Let's check the status of our instances to make sure they are running. Run the following command in your terminal window.

cf app bluechatter

This should result in something similar to the following.

requested state: started
instances: 5/5
usage: 128M x 5 instances
urls: bluechatter.mybluemix.net

     state     since                    cpu    memory          disk          
#0   running   2014-05-05 11:58:05 AM   0.0%   55.1M of 128M   25.5M of 1G   
#1   running   2014-05-05 11:58:33 AM   0.0%   55M of 128M     25.5M of 1G   
#2   running   2014-05-05 11:58:32 AM   0.0%   54.9M of 128M   25.5M of 1G   
#3   running   2014-05-05 11:58:33 AM   0.0%   54.8M of 128M   25.5M of 1G   
#4   running   2014-05-05 11:58:32 AM   0.0%   55.9M of 128M   25.5M of 1G

If some of your instances still say starting in the state column, wait a minute and execute cf app bluechatter again.

Easy, right?

ID yourself

Each instance of the application you are running in Bluemix has a unique instance ID. For our own sake, it would be nice to know which instance we are connecting to by surfacing that information in the UI of the BlueChatter application. Lets add some HTML to index.html and create a REST API that will return the instance ID so we can display that information to the user.

index.html

Open the index.html file in the public folder and find the div with the class footer. Add the following HTML to the div.

<p id="instance-id" style="display: none;">Instance ID: <span id="instance-id-value"></span></p>

This is where we will put the instance ID in the UI.

client.js

Open the client.js file in the public/javascripts folder. Within the document ready callback function, add the following JavaScript snippet:

  $.getJSON('/instanceId', function(response, statusText, jqXHR) {
    if(jqXHR.status == 200) {
      $('#instance-id').show();
      $('#instance-id-value').html(response.id);
    }
  });

Similar to how the poll function works, we are using jQuery to make an Ajax request to a REST endpoint named /instanceId. This will return a simple JSON object with the instance ID in it.

app.js

The final step is to add the server code for our new REST endpoint. Open app.js and add the following code to the file.

var instanceId = cfCore.app && cfCore.app != null ? cfCore.app.instance_id : undefined;
app.get('/instanceId', function(req, res) {
  if(!instanceId) {
    res.writeHeader(204);
    res.end();
  } else {
    res.end(JSON.stringify({
      id : instanceId
    }));
  }
});

Notice we are using the cf-env library again to get the instance ID. We are being very cautious when doing this because we could be running the application locally as well, so we check to make sure the proper properties are defined. Our REST endpoint will return a 204 (no content) if we are running locally, and we don't have an instance ID. If we do have an instance ID, it returns a simple JSON object containing the ID.


Testing on Bluemix

Let's deploy our new code and try our new functionality. From within the bluechatter directory, again run:

cf push bluechatter -m 128M

After the new code is deployed, open your favorite browser and go to the URL for your application. You should now notice the instance ID printed out in the footer.

Image shows instance ID

Open your second-favorite browser and again navigate to the URL for your application. Try to connect to a different instance of your application. (In other words, there should be different instance IDs in the two browser windows.) If you end up connecting to the same instance, just refresh your browser until you see a different ID in the browser.

Image shows different browser instance ID

Click to see larger image

Enter two different user names, like before, and try chatting. You should notice a problem, our app is no longer working. Why?

Back to the drawing board

What's going wrong? Why is our application failing when we scale it? The clue is in the details about how scaling works. When an application is scaled in Bluemix, Bluemix will create multiple instances of your application. Think of each instance as its own separate server on its own separate machine. None of the instances are sharing memory or even the same file system. Now let's look back at how we implemented our long polling. How are we storing the responses to the /poll endpoint? Can't remember? We are storing them in an array in memory. If each instance has its own memory, that means each instance has its own list of clients polling the server. None of the servers know about each others clients.

Don't freak out. There is a solution. Actually, there are probably many solutions to this problem, but I will elaborate on just one. We can notify the other instances of the message that has been sent from the client so the servers can in turn notify their clients. Something like a pub-sub architecture would work. Luckily, enough there is a service in the Bluemix catalog that will provide us with the pub-sub functionality we need.

Redis to the rescue

Redis is an extremely fast key-value store. Well, that is its primary use; it can do many other things, including implementing a pub-sub system. In the Bluemix catalog, there is a Redis service, provided by Redis Cloud, which we should be able to use with our application. In addition, there is a Redis library for Node.js, which we can use to communicate with our Redis Cloud service.

May I have a Redis service, please?

Let's create an instance of Redis Cloud we can use for our BlueChatter application. Bluemix can help us there. We just need to know some additional details about the Redis Cloud service. We can find them our from our trusty CLI tool. Run the following command in your terminal window.

cf marketplace

This command will give us some information — service name, plan and description, information about each service in the Bluemix catalog, etc. You should see one called rediscloud that has the following information.

rediscloud                 25mb                          Enterprise-Class Redis for Developers

This is all we need to know to create an instance of Redis from Redis Cloud for our BlueChatter application.

Run the following command to get a Redis instance.

cf create-service rediscloud 25mb redis-chatter

If that command completed successfully, you have an instance of Redis ready to use. That was easier than deploying our application. You can see the create-service command takes three parameters: the service name, the plan, and a name for the specific instance of the Redis Cloud service you choose. It can be anything you want; in this case, I chose redis-chatter. You can choose whatever name you want, but take note of it because we will need it later.

require('redis');

Now it is time to use Redis in our application code. Open package.json within the bluechatter directory. We need to add the Redis library to our dependencies. Replace the dependencies property with the code snippet below and save the package.json file.

  "dependencies": {
    "express": "3.4.8",
    "cf-env": "*",
    "redis": "*"
  }

Now open app.js in the bluechatter directory. We need to require the Redis library so we can use it in our server code. Add the following line at the end of the other require statements in app.js.

var redis = require('redis');
Redis service details

Before we continue, we need details like the host, port, and password in order to connect to the Redis server given to us by Redis Cloud. Details such as these are provided to us by the Redis instance we created in the previous section. They are stored within an environment variable called VCAP_SERVICES as a JSON object. One approach would be to access that environment variable and parse the JSON and pull it out. However, we can use the cf-env library to do all the hard work for us. Add the following two lines of code to app.js.

var redisService = cfEnv.getService('redis-chatter');
var credentials = !redisService || redisService == null ?  
{"host":"127.0.0.1", "port":6379} : redisService.credentials;

As shown, we are using the getService API of the cf-env library to access the service details. Notice that we pass the service name (redis-chatter), so the library can identify the specific instance of the service we want the details for. Within the object returned to us is a credentials property, which contains the host, port, and password we need to connect to the Redis service. Again, we are being careful when obtaining that property because if we are running the application locally, there will be no VCAP_SERVICES environment variable, so our redisService variable will be undefined. If it is undefined, we assume the user has a Redis server running locally at the default port (6379). To run the application locally, you can download and install a local Redis server from the Redis website.

Creating Redis clients

At this point, we are ready to create some clients we can use to talk to the Redis server. We need two clients — one to handle publishing our chat messages and the other to listen for new chat messages. Add the following code snippet to app.js.

var subscriber = redis.createClient(credentials.port, credentials.hostname);
subscriber.on("error", function(err) {
  console.error('There was an error with the redis client ' + err);
});
var publisher = redis.createClient(credentials.port, credentials.hostname);
publisher.on("error", function(err) {
  console.error('There was an error with the redis client ' + err);
});
if (credentials.password != '') {
  subscriber.auth(credentials.password);
  publisher.auth(credentials.password);
}

You can see we are creating two Redis clients with the host and port we got using the cf-env library and assigning them to variables names subscriber and publisher. In addition, if we have a password (again keeping in mind we may not have one if we are running locally), we authenticate both clients.

Let me know if there is any chatter

Let's make use of our subscriber client and listen for any chatter being published by other server instances. Add the following code at some point after the subscriber client has been created in app.js.

subscriber.on('message', function(channel, msg) {
  if(channel === 'chatter') {
    while(clients.length > 0) {
      var client = clients.pop();
      client.end(msg);
    }
  }
});
subscriber.subscribe('chatter');

This code adds an event listener to the subscriber client to listen for any message events. A message event will be triggered whenever something is published over pub-sub to the redis-chatter instance. The function handling the event takes a channel and the message being published. The channel is kind of like an ID for subscribers to listen on. It identifies the types of messages being published. Publishers will specify the channel ID whenever they publish a message. Our event-handler function only handles messages on the channel "chatter," so the first thing we do is make sure the channel variables equals chatter. If it does, we loop through our client array (remember, these are our long-polling requests) and respond to the requests with the message published to them. Finally, we tell our subscriber client we want to subscribe to the chatter channel. (You may be saying to yourself, "because we only subscribe and publish events on the chatter channel, why check the channel variable in our event handler?" We technically don't have to do that, but we are good programmers. We should anticipate that in the future we could publish events on other channels as well.)

Broadcasting chatter

At this point, our subscriber will never be called because no one is broadcasting any chatter. The perfect place to broadcast chatter is where the clients are sending us messages: the /msg REST endpoint. Replace the current /msg endpoint implementation within bluechatter/app.js with this new implementation.

app.post('/msg', function(req, res) {
  message = req.body;
  publisher.publish("chatter", JSON.stringify(message));
  res.end();
});

In our new implementation, we are no longer looping through the clients array and sending the chat message (this is now being done in the Redis subscriber event handler). We are simply getting the chat message from the POST body and publishing it on the chatter channel for our other servers, and ourselves, to send to the clients.

Scalable chatter

At this point, our application should handle being scaled with ease, but our deployment has become more complex by introducing the requirement of the Redis service. (Our application will not work in Bluemix without the Redis service bound to it.) Before we deploy our application, let's use a manifest to simplify our deployment process.

Nothing like simplifying your push

Manifests can take very verbose and error-prone Bluemix deployments and make them simple and consistent. In the bluechatter directory, create a new file called manifest.yml and add the following content to it.

applications:
- name: bluechatter
  memory: 128M
  command: node app.js
  services:
  - redis-chatter

You can see most of the parameters we specified in our cf push command have been moved to this file. In addition, we have a services property with the name of our instance of the Redis service. This tells Bluemix to bind the redis-chatter service instance to our application when deploying our app.

Taking our manifest for a spin

Let's try all this new shiny functionality out. From within the bluchatter directory, run cf push. If you see "App started," the cf push command completed successfully and we are ready to see if we fixed our problem. You should still have five instances of your application running, so open your two favorite browsers and connect to two different instances. Now when you test out the application, you should notice that everything is working again.

Image shows testing success

Click to see larger image

Tip: Try the application our on your favorite mobile device as well.

Image shows mobile test

Click to see larger image

Optional: Dealing with timeouts

One thing we need to look out for is timeouts on our polling requests. Suppose the browser sends a request to the server, but the server never responds back to the browser because there were no chat messages sent over a long period of time. The browser will timeout the request. We want to preempt that from happening.

A simple solution is to go through the client's array and respond with a 204 to everything the server has not responded to after every minute. This guarantees that the server never hangs onto a request for more the a minute and solves the timeout problem. At the same time, there could be situations where the browser sends a poll request that we respond back to immediately with a 204 because it came in right before the minute was up. We could some up with a most complex algorithm to make this more efficient, but for this simple example, it is not necessary. Open app.js and add the below setInterval call.

// This interval will clean up all the clients every minute to avoid timeouts
setInterval(function() {
  while(clients.length > 0) {
    var client = clients.pop();
    client.writeHeader(204);
    client.end();
  }
}, 60000);

Conclusion

We finally have an application that can scale in the cloud. You can see that we had to change the design of it to make sure it could handle being scaled, but the code we had to change was minimal. The patterns used in our application to allow it to scale can be applied to any language you want — the Java™ programming language, Ruby, PHP, etc. The take-away from this article to keep scaling in mind when writing your code. That way, when you deploy your application to the cloud and it comes time to scale, you will have confidence that your application will continue to work and not come crashing down.


BLUEMIX SERVICE USED IN THIS TUTORIAL:The SDK for Node.js runtime helps you develop, deploy, and scale server-side JavaScript apps with ease.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Cloud computing on developerWorks


  • Bluemix Developers Community

    Get samples, articles, product docs, and community resources to help build, deploy, and manage your cloud apps.

  • developerWorks Labs

    Experiment with new directions in software development.

  • DevOps Services

    Software development in the cloud. Register today to create a project.

  • Try SoftLayer Cloud

    Deploy public cloud instances in as few as 5 minutes. Try the SoftLayer public cloud instance for one month.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Cloud computing
ArticleID=972009
ArticleTitle=Build highly scalable applications with Node.js and Redis
publish-date=06272014