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.
...