Sample ImageTomcat Cluster Session Replication how to is a document that describes how we can setup a tomcat 5.5 cluster with apache web server frontend using in-memory session replication.
Tomcat 5.5.x has significant performance improvements than tomcat 5.0, mostly because of java 5 (from performance metrics taken with jmeter –
http://jakarta.apache.org/jmeter/  about 50% faster in compilation of classes/jsp/servlets). In our example we will use 1 apache instance that load balance requests along 4 instances of tomcat servers in 2 different servers. 

Our 2 test servers are server1, server2. Each one will have 2 instances of tomcat servers (tomcat1 and tomcat2) and server1 will have the apache web server instance.

 

Starting with the apache web server, we install / compile apache2.0.x in server1. I prefer to compile apache, for performance / security reasons, but an installation from packages (rpm / deb etc.) will do, too.

 

Then we compile the tomcat connector. We will use mod_jk (http://tomcat.apache.org/connectors-doc/ ). We can use a binary version, or compile from source. After we extract the source,

# cd jakarta-tomcat-connectors-version-src/jk/native
# CFLAGS="-DEAPI -O3" ./configure –with-apxs=/path_to_apache/bin/apxs –enable-DEAPI
# make
# make install
 

After successful compilation, in /apache_path/modules/ we should have the module compiled (mod_jk.so).

We need to add these lines in httpd.conf:

 

# Tomcat Configuration
LoadModule jk_module          modules/mod_jk.so
JkWorkersFile "conf/workers.properties"
JkLogFile "logs/mod_jk.log"
JkLogLevel info
JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"

# Tomcat Mapping
JKMount /*.jsp loadbalancer
JKMount /* loadbalancer
JkMount jkstatus
 

 

We also need to create a file named workers.properties in /apache_path/conf directory.

#
# workers.properties
#
# In Unix, we use forward slashes:
ps=/
# list the workers by name
worker.list=tomcat1, tomcat2, tomcat3, tomcat4, loadbalancer
# ————————
# 1st tomcat server
# ————————
worker.tomcat1.port=18009
worker.tomcat1.host=server1
worker.tomcat1.type=ajp13
# Specify the size of the open connection cache.
#worker.tomcat1.cachesize
#
# Specifies the load balance factor when used with
# a load balancing worker.
# Note:
#  —-> lbfactor must be > 0
#  —-> Low lbfactor means less work done by the worker.
worker.tomcat1.lbfactor=100
# ————————
# 2nd tomcat server
# ————————
worker.tomcat2.port=28009
worker.tomcat2.host=server1
worker.tomcat2.type=ajp13
# Specify the size of the open connection cache.
#worker.tomcat2.cachesize
#
# Specifies the load balance factor when used with
# a load balancing worker.
# Note:
#  —-> lbfactor must be > 0
#  —-> Low lbfactor means less work done by the worker.
worker.tomcat2.lbfactor=100

# ————————
# 3rd tomcat server
# ————————
worker.tomcat3.port=18009
worker.tomcat3.host=server2
worker.tomcat3.type=ajp13
# Specify the size of the open connection cache.
#worker.tomcat1.cachesize
#
# Specifies the load balance factor when used with
# a load balancing worker.
# Note:
#  —-> lbfactor must be > 0
#  —-> Low lbfactor means less work done by the worker.
worker.tomcat3.lbfactor=100
# ————————
# 4th tomcat server
# ————————
worker.tomcat4.port=28009
worker.tomcat4.host=server2
worker.tomcat4.type=ajp13
# Specify the size of the open connection cache.
#worker.tomcat2.cachesize
#
# Specifies the load balance factor when used with
# a load balancing worker.
# Note:
#  —-> lbfactor must be > 0
#  —-> Low lbfactor means less work done by the worker.
worker.tomcat4.lbfactor=100

# ————————
# Load Balancer worker
# ————————
#
# The loadbalancer (type lb) worker performs weighted round-robin
# load balancing with sticky sessions.
# Note:
#  —-> If a worker dies, the load balancer will check its state
#        once in a while. Until then all work is redirected to peer
#        worker.
worker.loadbalancer.type=lb
worker.loadbalancer.balanced_workers=tomcat1, tomcat2, tomcat3, tomcat4

  # Add the status worker to the worker list
  worker.list=jkstatus
  # Define a 'jkstatus' worker using status
  worker.jkstatus.type=status

#
# END workers.properties
#
 

 
In order to prepare tomcat installation, we install java5 jdk from http://java.sun.com/ and create a tomcat non-privileged user with the following options in user environment:

PATH="/java_path/bin:/java_path/jre/bin:$PATH"
JAVA_HOME="/java_path"
JAVA_OPTS=' -Xmx768m -Xms512m -Xss1024k -server'
export PATH JAVA_HOME JAVA_OPTS
 

 

We chose binary distributions for tomcat servers:
For both servers we do:

# cd /path_to install_tomcat
# tar zxvf tomcat-version.tar.gz
# mv tomcat-version tomcat1
# chown –R tomcat:root tomcat1
# cp -pr tomcat1 tomcat2
 

For tomcat1 in server1 configuration we change server.xml as follows:

<!– Example Server Configuration File –>
<Server port="18005" shutdown="SHUTDOWN">

  <Listener className="org.apache.catalina.core.AprLifecycleListener" />
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.storeconfig.StoreConfigLifecycleListener"/>

  <GlobalNamingResources>

    <Environment name="simpleValue" type="java.lang.Integer" value="30"/>

    <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="18080" maxHttpHeaderSize="18192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" redirectPort="18443" acceptCount="100"
               connectionLinger="-1" tcpNoDelay="true"
               connectionTimeout="20000" disableUploadTimeout="true" />

    <Connector port="18009"
               enableLookups="false" redirectPort="18443" protocol="AJP/1.3" />

     <Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">

        <Manager className="org.apache.catalina.session.StandardManager"/>

      <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"/>

      <Host name="localhost" debug="0" appBase="/var/www/"
       unpackWARs="false" autoDeploy="false" crossContext="false"
       cacheMaxSize="384000" cacheTTL="300" cachingAllowed="true"
       xmlValidation="false" xmlNamespaceAware="false">

          <Cluster className="org.apache.catalina.cluster.tcp.SimpleTcpCluster"
                name="mycluster"
                managerClassName="org.apache.catalina.cluster.session.DeltaManager"
                manager.expireSessionsOnShutdown="false"
                manager.stateTransferTimeout="30"
                manager.sendAllSessions="false"
                manager.sendAllSessionsSize="100"
                manager.sendAllSessionsWaitTime="5000"
                service.mcastBindAddress="10.10.10.10"
                receiverClassName="org.apache.catalina.cluster.tcp.ReplicationListener"
                receiver.tcpListenAddress="10.10.10.10"
                receiver.tcpListenPort="15010"
                receiver.tcpSelectorTimeout="100"
                receiver.tcpThreadCount="6"
                >
                <Membership
                className="org.apache.catalina.cluster.mcast.McastService"
                mcastAddr="228.0.0.214"
                mcastPort="45164"
                mcastFrequency="500"
                mcastDropTime="3000"/>
                <Sender
                className="org.apache.catalina.cluster.tcp.ReplicationTransmitter"
                replicationMode="fastasyncqueue"
                doTransmitterProcessingStats="false"
                doProcessingStats="false"
                doWaitAckStats="false"
                queueTimeWait="true"
                queueDoStats="false"
                autoConnect="false"
                waitForAck="true"
                ackTimeout="5000"
                resend="true"
                keepAliveTimeout="40000"
                keepAliveMaxRequestCount="-1"/>
                <ClusterListener className="org.apache.catalina.cluster.session.ClusterSessionListener" />
                <ClusterListener className="org.apache.catalina.cluster.session.JvmRouteSessionIDBinderListener" />
                <Valve className="org.apache.catalina.cluster.tcp.ReplicationValve"
                        filter=".*\.gif;.*\.js;.*\.css;.*\.png;.*\.jpeg;.*\.jpg;.*\.htm;.*\.html;.*\.txt;"
                        primaryIndicator="true"
                        />
                <Valve className="org.apache.catalina.cluster.session.JvmRouteBinderValve" enabled="true"/>
        </Cluster>

      </Host>

    </Engine>

  </Service>

</Server>

Now we can deploy our application in the path defined in server.xml (/var/www). As specified in http://tomcat.apache.org/tomcat-5.5-doc/cluster-howto.html :

 

  • All your session attributes must implement java.io.Serializable
  • Make sure your web.xml has the <distributable/> element or set at your <Context distributable="true" />
  • Make sure that all nodes have the same time and sync with NTP service!
  • Make sure that your loadbalancer is configured for sticky session mode.

You can use the following example session.jsp which shows the session id:

 

<!– session.jsp: example of session variable using Scriplets & HTML mixed –>

<%@page contentType="text/html"%>
<html>
<head><title>Session Example: session.jsp</title></head>
<body>

<!–
    Get the session id and display it to the user
–>

<%– THIS IS ONE WAY TO WRITE THE CODE, MIXING SCRIPLETS WITH HTML –%>
This is the session id <STRONG><%= session.getId()%></STRONG>
<BR>

<!– check to see if the session in new, if not display session information –>
<% if ( session.isNew() ) { %>
    This is a new session ! <BR>
<%} else { %>
    This is an existing session<BR>
<%}%>

</body>
</html>
 

Now start all tomcat instances and watch catalina.out log for messages like:

"…
org.apache.catalina.cluster.tcp.SimpleTcpCluster start
INFO: Cluster is about to start
org.apache.catalina.cluster.tcp.SimpleTcpCluster createDefaultClusterReceiver
INFO: Add Default ClusterReceiver at cluster server1
org.apache.catalina.cluster.tcp.SocketReplicationListener createServerSocket
org.apache.catalina.cluster.tcp.ReplicationTransmitter start
INFO: Start ClusterSender at cluster Catalina:type=Cluster,host=server1 with name Catalina:type=ClusterSender,host=server1
org.apache.catalina.cluster.mcast.McastService start
INFO: Sleeping for 2000 secs to establish cluster membership
org.apache.catalina.cluster.tcp.SimpleTcpCluster memberAdded
…"
 

If we open the url http://server1/session.jsp and try some restarts to tomcat instances we can watch session fail over from one instance to other without any interruption or session loss.

Now we have a robust 4-instance tomcat cluster with in-memory session replication. We can try  some performance tests and see that we have full load balancing  for our connections between apache and tomcat instances. We also have achieve session replication between those instances, for non-interruption in our services even if one instance or server fails.

Sources:

http://tomcat.apache.org/