Using JGroups Directly From JBoss AS 7 Component
JGroups is Bela Ban‘s piece of software for reliable message exchange that is highly configurable and can use either TCP or UDP as a transport protocol. Basically – you run the JGroups on number of clients, they form a cluster and they can send and receive messages within the cluster.
JGroups is used internally by JBoss Infinispan. Infinispan, however, unlike JGroups adds the distributed cache semantics (replicated / distributed modes, entries invalidation, transactional behavior, Map access API, etc.) It even allows you to use the cluster as a compute grid.
Infinispan in turn is used to provide JBoss AS 7 clustering functionalities. Therefore, it means that the underlying JGroups subsystem can and is configured using a standard JBoss AS 7 standalone*.xml file. You can access Infinispan cache from your Java EE component (e.g. EJB) without any problems as described here.
However, there are cases when you’d like to use just the underlying JGroups messaging instead of all the cache semantics Infinispan gives you. And here’s the place things are becoming more complicated. You can always use JGroups directly and store the configuration for it as an application-local resources. It might become arguable if this is or isn’t a violation of the Java EE spec which says that an application should not manage low-level connections, spawn threads, open sockets, etc. This is something that is better to be left to the application server – it also allows us to use one configuration file instead of spreading it across multiple places. So, the question is – how to access the JGroups subsystem from our EJB application? The whole solution involves few steps which will be described below. If you want to check the whole working project – take a look at my JGroups AS7 Github project.
1. Write Custom JBoss AS 7 Service Activator
This activator (JGroupsChannelServiceActivator.java
) will do two things:
- create the actual JGroups channel using JBoss protocol configuration,
- bind the newly created JGroups channel to the JNDI.
First part is done in JGroupsChannelServiceActivator#createChannel(-)
. I don’t know the ServiceActivator nor other internals of the JBoss AS 7 modules but from what you can read:
InjectedValue<ChannelFactory> channelFactory = new InjectedValue<>(); ServiceName serviceName = ChannelFactoryService.getServiceName(STACK_NAME); ChannelService channelService = new ChannelService(CHANNEL_NAME, channelFactory); target.addService(channelServiceName, channelService) .addDependency(serviceName, ChannelFactory.class, channelFactory).install();
it seems that it creates a new service (ChannelService
) and let the JBoss MSC automatically inject its dependent ChannelFactory
during the installation. The ChannelFactory
will use the UDP protocol stack. The second part is done in JGroupsChannelServiceActivator#bindChannelToJNDI(-)
and it binds the newly created Channel
instance to the JNDI under user-defined location. In our case it is java:jboss/channel/myChannel
.
2. Register the Activator
We now need to tell JBoss AS 7 to invoke our custom Activator. It is done using standardized JDK ServiceLoader API. In a nutshell it means we need to provide a META-INF/services/org.jboss.msc.service.ServiceActivator
file with fully qualified name of our activator class. Take a look at this example.
3. Add Required Modules to our Application
Ok, so we have an activator that should do the magic. If we’d try to deploy it as such we’ll get a bunch of ClassNotFoundExceptions. It is because the JBoss Modules. Our application is not packed with all those JBoss artifacts like JGroups, ServiceActivator API and the JNDI related classes. We don’t want to clutter our app with those libraries – we just want to define the dependencies on modules provided by JBoss AS 7 itself. We do it in the META-INF/jboss-deployment-structure.xml
. Note that we could do it in MANIFEST.MF Dependencies: section but Intellij IDEA doesn’t seem to be working with Maven generated MANIFEST.MF:
<?xml version='1.0' encoding='UTF-8'?> <jboss-deployment-structure> <deployment> <dependencies> <module name='org.jgroups'/> <module name='org.jboss.as.naming'/> <module name='org.jboss.as.clustering.jgroups'/> </dependencies> </deployment> </jboss-deployment-structure>
JGroups modules are required for accessing the JChannel, ChannelService, etc. The naming module is required for the JNDI binding code.
4. Develop the EJB Using JGroups Channel
JGroupsSampleDataProducer
is a Singleton EJB that shows how to access the JGroups channel. It’s rather simple because of the JNDI binding. We can just use:
@Resource(lookup = 'java:jboss/channel/myChannel') private JChannel channel;
and there it is. This EJB registers a timer that is invoked every 2 seconds and sends some random String message.
Note we didn’t have to explicitly start the JChannel
. We just inject it and use it straightaway. Take a look at the ChannelService
used in our activator from step 1. Its start method looks like:
@Override protected void start() throws Exception { (...) if (this.channel.getProtocolStack().findProtocol(STATE_TRANSFER.class, STATE.class, STATE_SOCK.class) != null) { this.channel.connect(this.id, null, STATE_TRANSFER_TIMEOUT); } else { this.channel.connect(this.id); }
So this service will connect to our channel automatically. Instead of using ChannelService
we could develop our own service that will be responsible for starting and stopping our channel or we could even move this responsibility to the actual user of the channel.
5. Deploy the EJB-JAR
We are now ready to deploy our application to JBoss AS 7 server. The most important part here is to make sure our server will be running with the appropriate configuration, which means one with JGroups protocol stack defined. It is done using <subsystem xmlns='urn:jboss:domain:jgroups:1.1'>
. I am using JBoss AS 7.1.1 and standalone-full-ha.xml
config.
**Note: ** Because the Intellij IDEA doesn’t allow you to easily change the configuration file for your JBoss AS as the Eclipse does, we will need to specify it using VM options: -Djboss.server.default.config=standalone-full-ha.xml
.
We also need to make sure JGroups will use IPv4 (it sometimes chooses the IPv6 which can lead to some weird and hard to solve problems.) To do so, add the -Djava.net.preferIPv4Stack=true
option to the Server configuration.
6. Run the Client Application
You can find a rather simple Client code here. It just connects to JGroups cluster using specified configuration file. Mind that multicast port number and address should be set to the same values for server and client. Also remember to add -Djava.net.preferIPv4Stack=true
VM option while running your client. Hope you’ll find this tutorial helpful and it’ll save you some configuration time. Great thanks to Bela Ban for a lot of important advice and Paul Ferraro for pointing me to relavant forum topics (like this or this one) regarding similar problems.
Reference: Using JGroups Directly From JBoss AS 7 Component from our JCG partner Piotr Nowicki at the Piotr Nowicki’s Homepage blog.