Important Notice: There is a more updated tutorial about Clustering with WildFly Application Server: Clustering WildFly Application Server
In this article we will show how to configure in a nutshell JBoss AS 7 for clustering. Then we will deploy a sample Java EE 6 application on the top of it.
Earlier JBoss users used to start a clustered server configuration by feeding the -c all parameter to the application server starter shell.
The AS 7 is pretty different from other server releases as it does not contain multiple server configuration directories (all, default, minimal ..). It just contains a set of configuration files each one including a different set of server modules.
The built-in cluster configuration file are standalone-ha.xml and standalone-full-ha.xml (the latter one including Corba support). So let’s start a demo cluster configuration composed of two nodes on the same machine.
So unzip two AS 7 distribution on a path of your choice, for example:
C:\jboss-as-7.1.1.Final.NodeA
C:\jboss-as-7.1.1.Final.NodeB
In order to start your cluster nodes you need to provide an unique node name and, since you are running multiple servers on the same machine specify a port offset so that you don’t have port conflicts between servers.
So we will start up NodeA with:
standalone -c standalone-ha.xml -Djboss.node.name=nodeA
and NodeB with:
standalone -c standalone-ha.xml -Djboss.socket.binding.port-offset=100 -Djboss.node.name=nodeB
Fine. Checkout the welcome page to see that everything works correctly:
http://localhost:8080 (nodeA) and http://localhost:8180 (nodeB)
Configuring Apache Web server to reach JBoss AS 7 cluster
There are more than one available choice to connect a Web server in front of JBoss AS 7 cluster. You can either choose the older mod_jk approach which provides a static view of your AS 7 cluster or, as we suggest, configure mod_cluster which provides additional advantages such as:
- Dynamic configuration of httpd workers
- Server-side load balance factor calculation
- Fine grained web-app lifecycle control
Mod_cluster Java libraries are built-in into JBoss AS 7 distribution so you don’t need to hack your server release. On the other hand, you need to add to your Apache server the native libraries required to proxy calls to JBoss AS. Download the latest mod_cluster binaries at:
http://www.jboss.org/mod_cluster/downloads/1-2-0-Final
The above modules can be found into the mod_cluster distribution under httpd/modules path. You have to place them into the Apache’s module folder.
Then we edit httpd.conf. We should add the modules:
LoadModule slotmem_module modules/mod_slotmem.so LoadModule manager_module modules/mod_manager.so LoadModule proxy_cluster_module modules/mod_proxy_cluster.so LoadModule advertise_module modules/mod_advertise.so
Also, please note we should comment out:
#LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
This one conflicts with cluster module. And then we need to make httpd to listen to public address so we could do the testing. Because we installed httpd on 192.168.1.0 host we will use the following available port:
Listen 192.168.1.0:6666 <VirtualHost 192.168.1.0:6666> <Directory /> Order deny,allow Deny from all Allow from 192.168.1. </Directory> KeepAliveTimeout 60 MaxKeepAliveRequests 0 ManagerBalancerName mycluster AdvertiseFrequency 5 </VirtualHost>
Now verify that the Apache JBoss connection works properly by typing: http://192.168.1.0:6666
Now we will deploy a variant of ourJava EE 6 example application which is composed of the following components:
Application Components
An EJB Stateful Bean which is used to store a cache of data, by exposing a Local interface
A JSF Managed Bean which acts as a layer between the EJB and the GUI
A JSF view which is used to collect data
Let’s start with coding the JSF Managed Bean class:
import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.enterprise.context.SessionScoped; import javax.inject.Inject; import javax.inject.Named; import com.sample.ejb.StatefulData; @SessionScoped @Named(value="manager") public class PropertyManager implements Serializable { @Inject StatefulData ejb; ArrayList cacheList = new ArrayList (); private String key; private String value; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public void save() { ejb.put(key, value); } public void clear() { ejb.delete(); } public List getCacheList() { return ejb.getCache(); } }
And this is the Local Stateful EJB class which is annotated as @Stateful @Clustered @SessionScoped
package com.sample.ejb; import java.util.ArrayList; import java.util.List; import javax.annotation.PostConstruct; import javax.ejb.LocalBean; import javax.ejb.Stateful; import javax.enterprise.context.SessionScoped; import org.jboss.ejb3.annotation.Clustered; import com.sample.model.Property; @Stateful @Clustered @SessionScoped @LocalBean public class LocalStatefulData implements StatefulData { private List<Property> cache; @Override @PostConstruct public void initCache(){ this.cache = queryCache(); if (cache == null) cache = new ArrayList<Property>(); } @Override public void delete(){ this.cache.clear(); } @Override public void put(String key,String value){ Property p = new Property(); p.setKey(key); p.setValue(value); this.cache.add(p); } @Override public List<Property> getCache() { return cache; } @Override public List<Property> queryCache(){ return cache; } }
For the sake of completeness we include also the Stateful SB interface:
package com.sample.ejb; import java.util.List; import com.sample.model.Property; public interface StatefulData { public abstract void delete(); public abstract void put(String key, String value); public abstract List<Property> getCache(); public abstract List<Property> queryCache(); }
Then, this is the model class which consists of a simple POJO:
package com.sample.model; import java.io.Serializable; public class Property implements Serializable{ private String key; private String value; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } }
Last piece of the application is the JSF home.xhtml page which is unchanghed from no clustered Java EE application:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:c="http://java.sun.com/jsp/jstl/core"> <h:head> </h:head> <h:body> <h2>Cluster demo on JBoss 7</h2> <h:form id="jsfexample"> <h:panelGrid columns="2" styleClass="default"> <h:outputText value="Enter key:" /> <h:inputText value="#{manager.key}" /> <h:outputText value="Enter value:" /> <h:inputText value="#{manager.value}" /> <h:commandButton actionListener="#{manager.save}" styleClass="buttons" value="Save key/value" /> <h:commandButton actionListener="#{manager.clear}" styleClass="buttons" value="Clear cache" /> <h:messages /> </h:panelGrid> <h:dataTable value="#{manager.cacheList}" var="item" styleClass="table" headerClass="table-header" rowClasses="table-odd-row,table-even-row"> <h:column> <f:facet name="header">Key</f:facet> <h:outputText value="#{item.key}" /> </h:column> <h:column> <f:facet name="header">Value</f:facet> <h:outputText value="#{item.value}" /> </h:column> </h:dataTable> </h:form> </h:body> </html>
That’s all. Remeber to add an empty beans.xml file under the WEB-INF folder so that the Weld dependencies are triggered automatically by AS 7.
Here’s how your project should look like:
Now launch your browser and test your Web application by pointing to the mod_cluster port that we have formerly configured:
http://192.168.1.0:6666/as7clustersample/home.xhtml