Ever need to have exactly one object (a single object method
invocation) in a cluster of many Oracle
WebLogic application servers that supports failover? The EJB 3.1 @Singleton annotation only guarantees an EJB singleton per JVM, and
creating a singleton in the traditional Java SE-fashions (see Joshua Bloch’s article at Dr
Dobbs) only guarantees a singleton per class loader.
WebLogic Server provides support for such a cluster-wide
singleton (here, scroll down to “Implementing
the Singleton Service Interface”), which I had the chance to experiment
with during the past week. The
documentation on this feature is adequate to get it running for the first time,
but I thought some additional detail around it might be useful.
What is the SingletonService, and what does it provide?
weblogic.cluster.singleton.SingletonService is an interface that you can implement
in your Plain Old Java Object. It’s not
applicable to EJBs, MDBs, or other objects whose lifecycles are managed by the
application server.
SingletonService provides
the two abstract methods activate() and deactivate(). activate()
is invoked when the class becomes the designated cluster-wide singleton
(i.e., server startup, failover and migration, or if the application is
re-deployed). deactivate() is more or less invoked on the inverse side of those operations:
Server shutdown, failover and migration, and application un-deployment.
This is really all the Singleton Service interface provides:
The invocation of the two implemented methods at the appropriate times, the
guarantee that only one instance is active, and the behavior of starting activate() on another server in the
cluster. This seemingly basic functionality
can be very powerful in the right use case, however.
What are some valid use cases for Singleton Service?
Singletons, even in the Java SE usage, usually require some
additional consideration and planning. It shouldn’t be surprising that a
singleton construct outside of the Java SE and EE APIs would require additional
care as well. The first step in correct
implementation is developing an understanding for what use cases the
cluster-wide singleton is (and isn’t) appropriate for.
This is not, by any means, a comprehensive list. Feel free to add use cases you have used it
for as well in the comments.
Timers / job schedulers in a cluster
The basic use case is getting a single server to fire off
timed events within a cluster and support failover in the event that the server
is turned off / unplugged / exploded. You only want a single server firing off these
timed events (e.g., JMS messages sent to a topic that prompts subscribers to
report some kind of status). This would
be along the lines of a cluster-aware cron
job. This is ordinarily not easy to achieve unless you configure a
heterogeneous cluster or an external cron
job– and that makes failover a concern.
SingletonService makes this use case relatively simple to
implement, since you only have it active on one server at a time and failover
is taken care of for you. James Bayer wrote
about this back in 2009, so you should be able to follow his example for
implementation. You may not want to have
the scheduler be the singleton
service, but you can use the singleton service to construct and start the
timer.
Use the SingletonService to handle other un-clusterable services
What if you wanted to run a service that cannot be clustered
in a meaningful way? How about a
Java-based email server? Perhaps you
need a file, FTP, or email client poller?
Using the activate() and deactivate() methods, you can create
the service within the cluster exactly once, and ensure that the service will
migrate over to another cluster member on server shutdown.
Single Source for State or Properties for Clustered Applications
Usually, using a database or an in-memory cache like Coherence
are more desirable options to store application properties, due to both reduced
complexity and ease of implementation. Using the database might not be an
option, however, since it’s possible that the connection to the database may be
transient or the information may be needed prior to connecting to the
database. It’s also possible that an
in-memory cache is not currently in the environment.
Given that you want a single place to store and update the
information, and not have to redeploy the application or restart the server to
get the change to take effect, you could use the Singleton Service to store
state / properties. In this case, I’ve
provided a sample.
My Example
My example is composed into two main parts: the Singleton Service
POJO (and its interface), and the application that invokes methods from the “JEE
world” – in this case, an Enterprise Java Bean.
I’ve got a couple of basic requirements: I need to reference the POJO
from anywhere in the cluster via JNDI, and I want it to carry some kind of
state.
Because I need to bind the object to JNDI, and access its
methods, I need to start with an interface that extends Remote (i.e., I am
going to use Remote Method Invocation).
Nothing profound, I just need modifiers to a private integer.
package com.darrel.samples;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface MySingletonServiceInterface extends Remote {
public void setMyValue(int value) throws RemoteException;
public int getMyValue() throws RemoteException;
}
Now I need to create the implementation:
package com.darrel.samples;
import java.io.Serializable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import weblogic.cluster.singleton.SingletonService;
public class MySingletonServiceClass implements
SingletonService, Serializable, MySingletonServiceInterface {
private static final long serialVersionUID = 3966807367110330202L;
private static final String jndiName = "MySingletonServiceClass";
private int myValue;
public int getMyValue() {
return myValue;
}
public synchronized void setMyValue(int myValue) {
this.myValue = myValue;
}
@Override
public void activate() {
System.out.println("activate triggered");
Context ic = null;
try {
ic = new InitialContext();
ic.bind(jndiName, this);
System.out.println("Object now bound in JNDI at " + jndiName);
myValue = 5;
} catch (NamingException e) {
myValue = -1;
e.printStackTrace();
}finally{
try {
if(ic != null) ic.close();
} catch (NamingException e) {
e.printStackTrace();
}
}
}
@Override
public void deactivate() {
System.out.println("deactivate triggered");
Context ic = null;
try {
ic = new InitialContext();
ic.unbind(jndiName);
System.out.println("Context unbound successfully");
}catch (NamingException e){
e.printStackTrace();
}
}
}
The basics are there – I implement the abstract methods from
Singleton Service. I use activate() to initialize a value for myValue. I also bind (and unbind upon deactivation)
the object in JNDI as “MySingletonService” (creative, I know). Concurrency is definitely an issue, so the synchronized modifier on setMyValue() is very, very necessary.
I created an EJB to access the POJO, and added web service
annotations for testing purposes.
package com.darrel.samples;
import javax.ejb.Stateless;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.naming.Context;
import javax.naming.InitialContext;
@WebService
@Stateless(mappedName="com.darrel.samples.SingletonTestingBean")
public class SingletonTestingBean
implements SingletonTestingBeanRemote,
SingletonTestingBeanLocal
{
int myValue;
public SingletonTestingBean() {}
@Override
@WebMethod
public String sayHelloInternalValue(String firstname) throws Exception {
System.out.println("sayHelloInternalValue invoked");
Context ctx = new InitialContext();
MySingletonServiceInterface mssc = (MySingletonServiceInterface)
ctx.lookup("MySingletonServiceClass");
myValue = mssc.getMyValue();
return "Hello " + firstname + ", my value is " + myValue;
}
@Override
@WebMethod
public int addInternalValue(int myInt) throws Exception {
Context ctx = new InitialContext();
MySingletonServiceInterface mssc = (MySingletonServiceInterface)
ctx.lookup("MySingletonServiceClass");
mssc.setMyValue(mssc.getMyValue() + myInt);
myValue = mssc.getMyValue();
return myValue;
}
}
Not much new here – a context lookup to the object, and
simple setters and getters. I’ve
excluded the remote and local interfaces for brevity.
To build and bundle our new Singleton Service and its interface
into a JAR, you will need to add weblogic.jar
to your class path at build time. Since
I used a plain Java project, I had to add weblogic.jar
as an external JAR to the project build path.
I added the resulting JAR to my WebLogic Domain’s /lib folder. The EJB project can be built with the JAR in
the build class path. In Eclipse, you
could do this via the “Required projects on the build path” dialog:
Now we need to configure the cluster for the Singleton Service.
In the WebLogic Administration Console, navigate to your cluster, and then to
the “Migration” tab. You will need to
have migration set up in some way, I used “Consensus” to avoid using a database
for this example, but your production model may have different needs entirely.
Now you need to navigate to the cluster’s “Singleton
Services” tab, and create a new Singleton Service.
Set your preferred server and we are ready for deployment
and testing. Deploy the EJB project to
the cluster. You can use the WebLogic Test Client to verify functionality, as
the EJB Web Service will provide web test points for you to use.
In my test, I used the addInternalValue() method on the EJB hosted on server1 to add 8, returning the total
value of 13.
Then, I used the sayHelloInternalValue()
method from server2, using the
argument “World” – note that the value displayed is 13, which implies that server2 is indeed invoking methods to
the same object as server1.
This is also a good time to look at the console output – take
a look at your server.out for the
preferred server, you should see the System.out.println() from the activate() method.
<Mar 2, 2012 5:25:20 PM CST> <Notice> <WebLogicServer> <BEA-000360> <The server started in RUNNING mode.>
activate triggered
Object now bound in JNDI at MySingletonServiceClass
To verify migration, try shutting down the preferred server
and you will then see the activate()
method’s print statements in the console output of one of the other servers in
your cluster. If you shut down each
server as the singleton becomes active on that server, you should see this
output in the console output in every server in the cluster.
Alternate methods of building and deploying
My initial attempt at deploying this Singleton Service was
by bundling it in a JEE utility JAR that was inside of the EJB EAR, and using
the weblogic-application.xml deployment descriptor of the EAR to register it as
an app-scoped singleton service. The xml
snippet regarding the singleton service would look like this:
<wls:singleton-service>
<wls:class-name>com.darrel.samples.MySingletonServiceClass</wls:class-name>
<wls:name>Appscoped_Singleton_Service</wls:name>
</wls:singleton-service>
This approach has the merit of not requiring a server
restart to get class changes to take effect after updating the Singleton
Service – you only need to redeploy the EAR.
It also simplifies the maintenance of your build path, since the
WebLogic System Libraries should already be there – unlike in my example, where
I had to add the weblogic.jar file
externally. Further, this eliminates the
step of modifying the cluster configuration in the Administration Console to
account for the singleton. Finally,
since you are no longer adding your singleton class to the $DOMAIN_HOME/lib, administrators will not have to directly modify
the class path to transition your application to production.
Implications for Use
Individually, the singleton service doesn’t benefit from the
linear performance scaling of the cluster, just the failover capabilities. This doesn’t mean that you can use the
singleton service to create scalable and performance-driven services, merely
that you can’t directly leverage the cluster to do so. For example: JMS servers exist as form of singleton
services within the cluster, but address scaling by hosting distributed
destinations. The constituent, physical
destinations of the logical distributed destination are individually hosted by
the singleton JMS servers. In this case,
however, quite a bit more is happening than simply registering a POJO in JNDI.
Concurrency is a consideration when providing access to
class members in the singleton that are not thread-safe. In the above example, I use a synchronized method to address
concurrent access to the data primitive class member. This is an observation that should be readily
apparent (certainly, it would also be true in the case of any other type of
singleton), but is worth mentioning as a warning.
“What are other people using this for?” you might ask. Of the customers I have encountered, a
majority have used the SingletonService as a means to create a High
Availability service out of a service that is fundamentally unable to be
clustered (like an FTP destination poller).
The SingletonService capability enabled them to avoid deploying the
application to a stand-alone managed server, and the failover capability allows
them to ensure that the service stays up as long as the cluster does.
Final Thoughts
I’d be curious to find out what you are using the Singleton
Service for – please leave your use case in the comments, if you can share.