Monday, February 21, 2011

Clustering Stateful Session Beans with GlassFish 3.1

Clustering will be the next big topic for the GlassFish 3.1 version. It's right ahead and you can already pick your RC4 build to experiment a bit. There are some screencasts for clustering the HTTPSession available. Arun Gupta did a great intro for GlassFish 3.1 Clustering, High Availability and Centralized Administration. So you can get a basic introduction into clustering, nodes, instances and domains. Up to today the documentation for all those features is still very limited. But this will change in the next few weeks. Here is, what I did to get a Stateful Session Bean failover scenario working with two instances on one node.

Preparation
As always: Get you GlassFish 3.1 RC4 up an running. Create a cluster (e.g. demoCluster) and add two instances (instance1 and instance2). Now start your domain and cluster. Write a simple EJB (e.g. MySessionBean) and make it implement a RemoteInterface (e.g. MySessionBeanRemote). My example has a simple String showInstance() business method which prints out the actual running instance:

Logger.getLogger(MySessionBean.class.getName())
.log(Level.INFO, "Running on: {0}",
System.getProperty("com.sun.aas.instanceName"));

Wrap your ejb-jar into an ear and deploy it. Make sure to check the "Availability" during deployment. This enables in-memory replication from one instance to the other.
Go and check if the EJB is registered with your instances:

%AS_INSTALL%/bin/asadmin list-jndi-entries demoCluster

You should see something like this:

node2:
java:global: com.sun.enterprise.naming.impl.TransientContext
[...]net.eisele.cluster.MySessionBeanRemote#
net.eisele.cluster.MySessionBeanRemote: javax.naming.Reference
[...]

node1:
java:global: com.sun.enterprise.naming.impl.TransientContext
[...]
net.eisele.cluster.MySessionBeanRemote#
net.eisele.cluster.MySessionBeanRemote: javax.naming.Reference
[...]

Up to now, this hardly was any problem. It's a simple EJB without anything else to tweak. Now let's go and write some clients.

Standalone Client
Coming from the old-school I am starting with a standalone client without any "magic". Create a Java project (e.g. StandaloneClient), add a Main class (StandaloneClient) and add the %AS_INSTALL%/glassfish/lib/gf-client.jar as a dependency. Make sure to reference the RemoteInterface (MySessionBeanRemote) somehow (Classpath or copying it to your projects source).

// get a simple no argument constructor InitialContext
InitialContext ic = new InitialContext();
// use the global jndi name as lookup string
String lookup = "java:global/ClusterTestEar/ClusterTestEar-ejb/MySessionBean!net.eisele.cluster.MySessionBeanRemote";
//lookup the jndi name
MySessionBeanRemote myEjb = (MySessionBeanRemote) ic.lookup(lookup);
//call showInstance
myEjb .showInstance();

Everything straight forward. No magic here. Compared with most of the other application servers it's unusual that you don't either have to specify any InitialContext properties or even add any cluster magic to the lookup string. But how does the client know about my instances and ports? That's simple. you have to specify them as VM Options:

-Dcom.sun.appserv.iiop.endpoints=
127.0.0.1:23701,127.0.0.1:23700


Application Client Container
The youth is probably unhappy about the standalone client. They are used to some kind of injection and those fancy annotation stuff. Ok. Here we go. You are calling for the ACC. The easiest way of creating one is to let your IDE do this. NetBeans generates a great acc project for you. It also has a main class and you can simply:

// inject you ejb ...
@EJB
private static MySessionBeanRemote myEjb;
[...]
// and use it
myEjb .showInstance();

Great. But here it goes. NetBeans itself does not have any kind of cluster deployment support, but the ACC project was designed to be deployed. So, even if you are not in need doing so, the "Run File" NetBeans command fails. So you have to do this manually. First part is to Tell the ACC where to find the remote EJB.
If you are not going to write a webstart based scenario (which is exactly what we are NOT going to do :)), you can simply pick a copy of the %AS_INSTALL%\domains\clusterdomain\config\sun-acc.xml and put it to your projects folder. You need to edit it and make it reflect your instances:

<target-server name="MYNODE" address="localhost" port="23701"/>
<target-server name="MYNODE" address="localhost" port="23700"/>

The %AS_INSTALL%\bin\appclient.bat/sh script is there to help you executing the ACC. Build a jar from your appclient project and run it:

appclient -client %YOUR_DIST_FOLDER%\ClusterAppClient.jar -xml %YOUR_DIST_FOLDER%\sun-acc.xml

Done. Now you have everything working. The new-school and the old-school way of accessing remote EJBs.

Clustering / Failover
Now let's test the failover capabilities. Add a second business method call to your clients and do anything to make the client pause between them. Either to automatically continue after a few seconds or on key-press.
Now let's start the client. It prints out to which instance this client is connected. Assume it tells you something like this:

Running on: instance1

Now switch over to your asadmin console and enter the needed stop-instance command to shutdown the instance the client is connected to. In this case:

asadmin> stop-instance instance1

If it is stopped, press a key or wait for the client script to continue. and see, that the second call is redirected to the second node:
Running on: instance2

Failover works. Perfect :) Thanks to Oracle's Cheng and Tim for answering my questions during research!

Further Reading
Oracle GlassFish Server 3.0.1 Application Development Guide Chapter 11 Developing Java Clients
GlassFish Community EJB FAQ