Sunday, October 9, 2011

Tomcat: Clustering and Load Balancing with HAProxy under Ubuntu 10.04 - Part 2

Review


In the previous section, we've set-up a simple environment containing a clustered Tomcat instances and HAProxy for load balancing. In this section, we will test our load balancing environment and explore various strategies to improve our setup.

Table of Contents


  1. Setting-up the Environment
    • Download Tomcat
    • Configure Tomcat
    • Run Tomcat
    • Download HAProxy
    • Configure HAProxy
  2. Load Balancing
    • Default Setup
    • Sharing Sessions
    • Configure Tomcat to Share Sessions
    • Retest Session Sharing
    • Session Sharing Caveat
    • Sharing Sessions
  3. HAProxy Configuration
    • Configuration File
    • Logging

Load Balancing


Default Setup

After downloading and installing Tomcat and HAProxy, we will now test the default load balancing

Open a browser and visit the following link:
http://localhost/

It should display the following page:


Notice we did not indicate any port. By default the browser will use port 80 for HTTP requests. The previous link is equivalent to:
http://localhost:80/

This means HAProxy is able to redirect our requests from port 80 to the Tomcat instances. If we check the HAProxy logs, we can see that the requests is redirected to tomcat1:
localhost haproxy[4530]: 127.0.0.1:42377 [06/Oct/2011:07:50:57.054] http-in servers/tomcat1 0/0/0/2/28421 200 25030 - - --NN 0/0/0/0/0 0/0 "GET / HTTP/1.1"

Let's pretend that tomcat1 has failed by shutting it down manually. To shutdown tomcat1, run the following command:
sudo /usr/local/tomcat-7.0.21-server1/bin/shutdown.sh

HAProxy's stats page should display that tomcat1 is dead. To display the stats page, open a browser, and visit the following link:
http://localhost/admin?stats


Now, let's check if we can still access the main page. Open a browser and visit the previous link:
http://localhost/

You should see the following page:


Notice the web page is still available! It means HAProxy is able to redirect our request from an inactive server to an active one.

If we check the HAProxy logs, it shows that our request has been redirected to tomcat2:
localhost haproxy[4530]: 127.0.0.1:56619 [06/Oct/2011:07:58:17.761] http-in servers/tomcat2 17/0/0/2/27825 200 13075 - - --NN 0/0/0/0/0 0/0 "GET / HTTP/1.1"

Let's turn off tomcat2. This means all our servers are down! Visit the localhost page again, and we should get the following response:


The web page is down! HAProxy's stat page shows that the Backend servers are down:


Sharing Sessions


If we are serving a web page that holds session information we assume that information is still available regardless if tomcat1 or tomcat2 is down.

Imagine a shopping cart. You're selecting items in a page. Behind the scenes the server you're working at has crashed. You expect the original shopping cart information is still intact. Otherwise, you'll start again from scratch.

Let's verify this behavior by examining the sample applications within the Tomcat examples directory. These examples are built-in to Tomcat when we initially installed it.

Before we proceed, please make sure your environment is as follows:
ServerStatus
Tomcat 1Down
Tomcat 2Up

The open up a browser, and visit the following page:
http://localhost/examples/jsp/jsp2/simpletag/hello.jsp

This is what you should see:


This application is one of the built-in examples included in the Tomcat installation. In my computer, this application resides at:
/usr/local/tomcat-7.0.21-server1/webapps/examples

I'm going to examine the session ID returned by this page by using Google Chrome's Developer Tools (see http://code.google.com/chrome/devtools/). Here's an actual screenshot:


The session ID reads 697E0084595762C85952E2AFEB7B56FD. If you're running this guide with an actual Tomcat, your session ID will vary.

Now, let's change our environment. Before we proceed, make sure this is your environment:
ServerStatus
Tomcat 1Up
Tomcat 2Down

Open up a browser, and visit the following page again:
http://localhost/examples/jsp/jsp2/simpletag/hello.jsp

It should display the same page still. Let's examine the session ID returned by this second request:


The session ID reads E501914ABC8DD2F2EC82A4B5123B51AA in the Response Header section; whereas it reads 697E0084595762C85952E2AFEB7B56FD in the Request Header section.

If we refresh the page, the Request Header now has E501914ABC8DD2F2EC82A4B5123B51AA and the original session ID 697E0084595762C85952E2AFEB7B56FD is gone forever. This means when we shutdown tomcat2, the session ID is not transferred from tomcat1.

Although we're seeing the same page, we're actually operating in different sessions. Imagine if this is a shopping cart. Suddenly, all your orders are gone! Time to file a support ticket!

How do we resolve this issue? The solution is simple. Enable session sharing. How? We follow the instructions given in the Apache Tomcat 7' Clustering/Session Replication HOW-TO reference.

Configure Tomcat to Share Sessions


The key to enable session sharing is to declare two XML elements: one in your application's web.xml (1) and the other in Tomcat's server.xml (2):

1. <distributable>
2. <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">

Let's declare those two XML elements in our "Hello World SimpleTag Handler" example. It's important that we declare those two elements in all our Tomcat instances where our application resides.

Let's do that now.

1. Go to your Tomcat 1's directory, and find the examples directory. In my computer, the directory is:
/usr/local/tomcat-7.0.21-server1/webapps/examples

2. Under WEB-INF, open web.xml and declare a <distributable> element. Place it just above the filter elements. See screenshot below:


3. Next, edit the server.xml. In my computer, this translates to
/usr/local/tomcat-7.0.21-server1/conf

Declare a <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"> element.
Place this element just below the Engine element. See screenshot below:


We've configured tomcat1. Now, configure tomcat2 by repeating the same steps.

Retest Session Sharing


After configuring both Tomcat instances, we need to restart them so that the changes will take effect.

Now, update your environment, and make sure it follows this scenario:
ServerStatus
Tomcat 1Down
Tomcat 2Up

Open a browser and visit the following page:
http://localhost/examples/jsp/jsp2/simpletag/hello.jsp

Using Google Chrome's Developer Tools, the session ID is 16E9D9B83CFF02196DBC794CE3E0AB3D



Update your environment, and make sure it follows this scenario:
ServerStatus
Tomcat 1Up
Tomcat 2Down

Again, open a browser and visit the following page again:
http://localhost/examples/jsp/jsp2/simpletag/hello.jsp

Using Google Chrome's Developer Tools, the session ID reads 16E9D9B83CFF02196DBC794CE3E0AB3D


Notice we have the same session ID!. This means our session information has been successfully retained and reused across our Tomcat instances.

Session Sharing Caveat


Everything seems fine now. However, I want to emphasize an important requirement with session sharing. To understand what I meant, let's run another built-in example application.

Open a browser, and visit the following page:
http://localhost/examples/jsp/sessions/carts.html

It should display a shopping cart:


Try adding an item. Immediately, an exception will be thrown:


The exception reads:
java.lang.IllegalArgumentException: setAttribute: Non-serializable attribute cart

Why are we getting this error? If we study the Tomcat 7 reference for clustering, we will find the following information:

To run session replication in your Tomcat 7.0 container, the following steps should be completed:

- All your session attributes must implement java.io.Serializable
- Uncomment the Cluster element in server.xml
- Make sure your web.xml has the element

Source: http://tomcat.apache.org/tomcat-7.0-doc/cluster-howto.html

The reference is telling us to ensure that our session attributes are serializable! Based on the error message, our cart is not serializable.

Let's examine the source code of this cart class. You can find the source code within your Tomcat examples folder. In my computer, this translates to:
/usr/local/tomcat-7.0.21-server1/webapps/examples/WEB-INF/classes/sessions/DummyCart.java

Here's the source code:


To make this class serializable, we just implement the java.io.Serializable class as follows:


You can compile this by yourself. Or you can download my patched version of DummyCart.class (click here to download). To use this patch, follow these steps:

  1. Go to your Tomcat examples directory. In my computer this translates to:
    /usr/local/tomcat-7.0.21-server/webapps/examples/WEB-INF/classes/sessions/
  2. Replace the old DummyCart.class with the patched version. Alternatively, rename the old one instead of deleting it.
  3. Repeat the previous steps to all your Tomcat instances.
  4. Restart all Tomcat instances.
Let's revisit our shopping cart. Try adding an item. Notice you're now able to add an item without any exceptions.


If we check the HAProxy logs, our request went to tomcat2
http-in servers/tomcat2 890/0/0/6/30760 304 3109 - - --NN 0/0/0/0/0 0/0 "GET /examples/jsp/sessions/carts.html HTTP/1.1"

The session ID is 84061AA7EF1EF6CADE7489113700481E as shown in the Google Developer tool (I have omitted the screenshot).

Let's turn off tomcat2 and add a new item in the shopping cart.

You should be able to add a new item:

HAProxy logs show that we're now using tomcat1 instance:
http-in servers/tomcat1 0/0/0/35/34234 200 2924 - - --IN 0/0/0/0/0 0/0 "GET /examples/jsp/sessions/carts.jsp?item=Switch+blade&submit=add HTTP/1.1"

Our session ID is still 84061AA7EF1EF6CADE7489113700481E as shown in the Google Developer tool:


Try switching servers off and on. Just make sure there's at least one server running. Notice the session ID never changes.

Next Section


We've successfully implemented load balancing using HAProxy and session sharing among our Tomcat instances. We've learned how to troubleshoot session IDs by using Google Chrome's Developer Tools. In the next secion, we will configure HAProxy logging so that we can easily troubleshoot problems arising from this tool. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Tomcat: Clustering and Load Balancing with HAProxy under Ubuntu 10.04 - Part 2 ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

7 comments:

  1. It's really a nice tutorial and applauds to you effort. But how do you handle stickey session when set to true for session replication which the session still persists though the application is not available(404 error) having tomcat running. I am facing the same problem of 404 error even after application is back available(during deployment) which I could only recover by restarting that particular node and thus the request the send to other available node.
    Do you have any tips for this issue?

    ReplyDelete
  2. Hi,
    I configured load balancing and it is working but session sharing is not working for me, actually session id is changing after 2 or 3 requests. Please help me.

    ReplyDelete
  3. really its awesome tutorial

    ReplyDelete
  4. I have read your blog its very attractive and impressive. I like it your blog.

    Spring online training Spring online training Spring Hibernate online training Spring Hibernate online training Java online training

    spring training in chennai spring hibernate training in chennai

    ReplyDelete
  5. Thank you for the post. Is there a way to add one more wrinkle to this example, which is authentication? We have the exact setup you illustrate, except we are using form-based authentication, and the session consists of JSESSIONID only.

    Any resource information or suggestion would be greatly appreciated. Very difficult to find forums or documentation when using Tomcat's authenticated sessions.

    ReplyDelete