Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Clustering JSR-168 Portlet Applications in Tomcat

 

John A. Lewis
Unicon, Inc.

 

Introduction

JSR-168 Portlet applications represent a special challenge when it comes to clustering within Tomcat (or any other servlet container, for that matter).  In order to effectively cluster web applications, session data must be replicated or shared between the nodes in the cluster.  Otherwise, the user experiences a complete loss of context during a node failover.  While Tomcat has provided session replication for quite some time, it has not supported replication of session changes resulting from a cross-context call from one webapp to another.

...

Code Block
<Server port="8006" 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>

    <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="8081" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" acceptCount="100"
               connectionTimeout="20000" disableUploadTimeout="true"
               emptySessionPath="true" />

    <Connector port="8010"
               enableLookups="false" redirectPort="8081" protocol="AJP/1.3"
               emptySessionPath="true" />

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

      <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.cluster.tcp.SimpleTcpCluster"
                 managerClassName="org.apache.catalina.cluster.session.DeltaManager"
                 expireSessionsOnShutdown="false"
                 useDirtyFlag="true"
                 notifyListenersOnReplication="true">

            <Membership
                className="org.apache.catalina.cluster.mcast.McastService"
                mcastAddr="228.0.0.4"
                mcastPort="45564"
                mcastFrequency="500"
                mcastDropTime="3000"/>

            <Receiver
                className="org.apache.catalina.cluster.tcp.ReplicationListener"
                tcpListenAddress="auto"
                tcpListenPort="4002"
                tcpSelectorTimeout="100"
                tcpThreadCount="6"/>

            <Sender
                className="org.apache.catalina.cluster.tcp.ReplicationTransmitter"
                replicationMode="pooled"
                ackTimeout="15000"/>

            <Valve className="org.apache.catalina.cluster.tcp.ReplicationValve"
                   filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.css;.*\.txt;"/>

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

            <ClusterListener className="org.apache.catalina.cluster.session.ClusterSessionListener"/>

        </Cluster>

      </Host>

    </Engine>

  </Service>

</Server>

 

The important differences here from the standard server.xml are the following:

...

Code Block
<Context path="/pluto" crossContext="true" />

 

The definition of 'crossContext="true"' is critical here -- this allows the Pluto Portal Driver to make calls into the portlets running inside other webapps.

...

Code Block
<servlet>
        <servlet-name>TestPortlet1</servlet-name>
        <servlet-class>org.apache.pluto.core.PortletServlet</servlet-class>
        <init-param>
            <param-name>portlet-class</param-name>
            <param-value>org.apache.pluto.portalImpl.portlet.TestPortlet</param-value>
        </init-param>
        <init-param>
            <param-name>portlet-guid</param-name>
            <param-value>testsuite.TestPortlet1</param-value>
        </init-param>
        <security-role-ref>
            <role-name>plutoTestRole</role-name>
            <role-link>tomcat</role-link>
        </security-role-ref>
    </servlet>

    <servlet>
        <servlet-name>TestPortlet2</servlet-name>
        <servlet-class>org.apache.pluto.core.PortletServlet</servlet-class>
        <init-param>
            <param-name>portlet-class</param-name>
            <param-value>org.apache.pluto.portalImpl.portlet.TestPortlet</param-value>
        </init-param>
        <init-param>
            <param-name>portlet-guid</param-name>
            <param-value>testsuite.TestPortlet2</param-value>
        </init-param>
        <security-role-ref>
            <role-name>plutoTestRole</role-name>
            <role-link>tomcat</role-link>
        </security-role-ref>
    </servlet>

    <servlet-mapping>
        <servlet-name>extAppScopedTest</servlet-name>
        <url-pattern>/test/extAppScopedTest</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
      <servlet-name>includeTest</servlet-name>
      <url-pattern>/tests/include</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>TestPortlet1</servlet-name>
        <url-pattern>/TestPortlet1/*</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>TestPortlet2</servlet-name>
        <url-pattern>/TestPortlet2/*</url-pattern>
    </servlet-mapping>

 

This should come between the last existing <servlet> declaration and the existing <security-role> declaration.

...

Code Block
<Context path="/session-test" crossContext="true" />

 

Even though portlet webapps do not need to be declared as cross-context for normal communication with the portal, they must be declared as cross-context for the session replication to work properly.

...

Code Block
LoadModule jk_module modules/mod_jk.so

JkWorkersFile   conf/workers.properties
JkLogFile       logs/mod_jk.log
JkLogLevel      warn

JkMount /jkstatus/* status

JkMount /pluto/* loadbalancer

JkMount /testsuite/* loadbalancer

JkMount /session-test/* loadbalancer

 

Then create a file in the conf directory of your Apache installation called 'workers.properties'.  This file should contain the following configuration:

Code Block
worker.list=status,loadbalancer

worker.status.type=status

worker.loadbalancer.type=lb
worker.loadbalancer.balanced_workers=tomcatA,tomcatB
worker.loadbalancer.sticky_session=1

worker.tomcatA.port=8010
worker.tomcatA.host=localhost
worker.tomcatA.type=ajp13
worker.tomcatA.lbfactor=1

worker.tomcatB.port=8011
worker.tomcatB.host=localhost
worker.tomcatB.type=ajp13
worker.tomcatB.lbfactor=1

 

With both of your Tomcat instances up and running, restart Apache.  Now browse to http://localhost/jkstatus/ and you should see the details about your load balancer and your two Tomcat workers.  The tomcatA and tomcatB workers should both have status as 'OK'.  If this is not the case, resolve this before proceeding.

...