Clustering Apache Tomcat Failover

Probably you’ve already heard about this topic and it’s very simple to apply. Whatever you’ve already known or not, I’d like to set a little list about how configure a couple of tomcat application server with apache server on your developer enviroment.

It can be very usefull for testing clustering application when something goes wrong…

First some definition.

Tomcat is “an open source web server and servlet container” (from Wikipedia) developed by Apache foundation. We can put here jsp, servlet, web services, etc…

Apache Http server is a web server (not servlet container) developed by Apache foundation. We can put here html pages, images, etc..

What’s the purpose of using this combination of Apache Http server and Tomcat server? Typically  this combination is used for building the clustering, as the diagram below.

This configuration defines a load balance cluster. The two Tomcat instances are called by the Apache http server and, whether one of this fails, the other one continues to be called by the http server. This is the fail over solution.

Now, take a look about how it’s built. My folder structure is the follow; under the folder (C:\Program Files\Apache Software Foundation\) I’ve installed

Apache2.2
apache-tomcat-6.0.33-8109
apache-tomcat-6.0.33-8209

Let’s go to configure it.

Under the apache folder, open the file conf/httpd.conf. Insert or uncomment the rows:

LoadModule jk_module modules/mod_jk.so
<IfModule jk_module>
JkWorkersFile "C:/Program Files/Apache Software Foundation/Apache2.2/conf/workers.properties"
JkLogFile "C:/Program Files/Apache Software Foundation/Apache2.2/logs/mod_jk.log"
JkLogLevel info
JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "
JkMount /* loadbalancer
</IfModule>

I’ve added the apache tomcat connector (mod_jk). More information are available on http://tomcat.apache.org/connectors-doc/index.html.

Next step is create the worker.properties file inside apache configuration. Create worker.properties file under the same previous directory and put this inside.

worker.list=loadbalancer
worker.tomcata.type=ajp13
worker.tomcata.host=localhost
worker.tomcata.port=8109
worker.tomcata.lbfactor=1
worker.tomcata.cachesize=10
worker.tomcata.cache_timeout=600
worker.tomcata.socket_keepalive=1
worker.tomcata.socket_timeout=300
worker.tomcata.redirect=tomcatb
worker.tomcatb.type=ajp13
worker.tomcatb.host=localhost
worker.tomcatb.port=8209
worker.tomcatb.lbfactor=1
worker.tomcatb.cachesize=10
worker.tomcatb.cache_timeout=600
worker.tomcatb.socket_keepalive=1
worker.tomcatb.socket_timeout=300
worker.tomcatb.activation=disabled
worker.loadbalancer.type=lb
worker.loadbalancer.balanced_workers=tomcata,tomcatb

I’ve defined two Tomcat instances (TomcatA and TomcatB). Take notice about “worker.loadbalancer.type=lb”. This means that I’m using worker named “loadbalancer” that loadbalances several Tomcat processes transparently.

That’s all for Apache http server.  To configure apache Tomcat I edit the file conf/server.xml adding the cluster configuration.

<Server port="8100" shutdown="SHUTDOWN">

<GlobalNamingResources>
   <Resource name="UserDatabase" auth="Container"
      type="org.apache.catalina.UserDatabase"
      description="User database that can be updated and saved"
      factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
      pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>

<Service name="Catalina">
   <Connector port="8180" protocol="HTTP/1.1"
      connectionTimeout="20000"
      redirectPort="8443" />

   <!-- Define an AJP 1.3 Connector -->
   <Connector port="8109" protocol="AJP/1.3" redirectPort="8443" />

   <Engine name="Catalina" defaultHost="localhost" jvmRoute="worker1">
      <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
         resourceName="UserDatabase"/>

      <Host name="localhost"  appBase="webapps"
         unpackWARs="true" autoDeploy="true"
         xmlValidation="false" xmlNamespaceAware="false">

         <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
                 channelSendOptions="8">

          <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>

          <Channel className="org.apache.catalina.tribes.group.GroupChannel">
            <Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="228.0.0.4"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="auto"
                      port="4000"
                      autoBind="100"
                      selectorTimeout="5000"
                      maxThreads="6"/>

            <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
              <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
            </Sender>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
          </Channel>

          <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
                 filter=""/>
          <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>

          <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
                    tempDir="/tmp/war-temp/"
                    deployDir="/tmp/war-deploy/"
                    watchDir="/tmp/war-listen/"
                    watchEnabled="false"/>

          <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
          <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
        </Cluster>
      </Host>
   </Engine>
</Service>
</Server>

The only difference between the other Tomcat configuration file is the connector port of ajp protocol.

TomcatA

<Connector port="8109" protocol="AJP/1.3" redirectPort="8443" />

TomcatB

<Connector port="8209" protocol="AJP/1.3" redirectPort="8443" />

Last point is useful for checking on which Tomcat instance is serving our request. To do it we need to modify the index.html file under Tomcat example folder.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD><TITLE>Apache Tomcat Examples</TITLE>
<META http-equiv=Content-Type content="text/html">
</HEAD>
<BODY>
<P>
<H3>Apache Tomcat Examples</H3>
<P>WELCOME ON TOMCAT A</P>
<ul>
<li><a href="servlets">Servlets examples</a></li>
<li><a href="jsp">JSP Examples</a></li>
</ul>
</BODY></HTML>

The same thing should be done on the other instance B.

Now it’s time to running the Tomcat instances and the Apache instance. The test is very simple.

  1. Browse http://localhost/examples/; you can see the previous page modified.
  2. Stop one of the tomcat instance
  3. Browse again http://localhost/examples/; you can see the previous page modified from the only instance survived.

Very well. I think that could be very usefull on development enviroment because it’s very short to configure, not much complicated, and could emulate a production enviroment for debbuging purpose.

UPDATE

Another useful and configurable feature is the cluster session replication.

A user has his credentials stored in session and one node fails. Without this feature, the user with session in the node broken would lose their credentials and, probably, recognized as anonymous user. With this feature enabled the user goes on to be known into the web application in another instance.

Not too bad?

To enable this feature in Tomcat 6, it’s necessary to move the configuration node <Manager> into context.xml in every tomcat nodes. So, the new context file looks like this:


<Context>

    <!-- Default set of monitored resources -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
	
	 <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>
                   
</Context>

This update enable the session replication, but another step is necessary; adding the node <distributable/> at the web.xml of every Web Applications that need to have replicated session.


<web-app xmlns="..."
   version="2.5"> 
...
 <distributable/>
</web-app>

I used a little jps page to test.


<html>
<head>
<title>Session Replication</title>
</head>
<body>
<%
String sessionValue = request.getParameter("sessionvalue");
if (sessionValue!=null)
session.setAttribute("ValueSet",sessionValue);
%>
Welcome <font color="red"><%= session.getAttribute("ValueSet") %></font>
<p>
<form name="valuereplication" method="post">
Value:&nbsp;<input type="text" id="sessionvalue" name="sessionvalue"><br>
<input type="submit" value="Send">
</form>
</p>
</body>
</html>

Run the cluster and set a session value at the url http://localhost/examples/sessionreplication.jsp. Stopping one node and refreshing the page you’ll see the same value due of session replication.

Advertisements

2 thoughts on “Clustering Apache Tomcat Failover

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s