Saturday, September 30. 2017
Clustered stateful EJB in Wildfly called from standalone java client
Long time ago I posted an article about configuring an EJB in a clustered glassfish. During this summer I had to deal with some issues about this subject but using Wildfly. I discovered that, in this container, there are several possibilities of doing a remote call to an EJB, but only one of them is the good one. So this entry is going to setup this good configuration step by step. This same option is a little different when the client is another Wildfly server (a wildfly frontend communicates with another Wildfly backend in which the EJBs are running) than when the client is a standalone java app or another container. Today's entry covers the last case, using a non-wildfly client.
Installing the Wildfly cluster
First a clustered wildfly backend needs to be installed. Both instances are going to be running in the same machine, using a port offset in the one of them. The steps are the following.
Install the software, create an admin user for the management console and start the domain.
wget http://download.jboss.org/wildfly/10.1.0.Final/wildfly-10.1.0.Final.zip unzip wildfly-10.1.0.Final.zip cd wildfly-10.1.0.Final/bin ./add-user.sh -u admin -p adminadmin ./domain.sh
By default wildfly generates some configuration (groups and servers) that I usually remove. From now on the jboss-cli.sh command is used to configure the domain.
./jboss-cli.sh --connect /host=master/server-config=server-one:stop(blocking=true) /host=master/server-config=server-two:stop(blocking=true) /host=master/server-config=server-three:stop(blocking=true) /host=master/server-config=server-one:remove() /host=master/server-config=server-two:remove() /host=master/server-config=server-three:remove() /server-group=main-server-group:remove() /server-group=other-server-group:remove()
With an empty domain two servers are created in the ha profile. In order to have a cluster you have to use at least this profile (full-ha profile would be also ok). Please take into account that the second server will use an offset of 10000. Besides a property jboss.node.name is set to each instance with the name (this property will be used later in the EJB to know which instance is executing it).
/server-group=ha-server-group:add(profile=ha,socket-binding-group=ha-sockets) /host=master/server-config=ejb-server-1:add(group=ha-server-group) /host=master/server-config=ejb-server-2:add(group=ha-server-group,socket-binding-port-offset=10000) /host=master/server-config=ejb-server-1/system-property=jboss.node.name:add(value=ejb-server-1,boot-time=true) /host=master/server-config=ejb-server-2/system-property=jboss.node.name:add(value=ejb-server-2,boot-time=true) /host=master/server-config=ejb-server-1:start(blocking=true) /host=master/server-config=ejb-server-2:start(blocking=true)
The cluster will use default configuration. Wildfly use jgroups for cluster communication and this library can be configured in different ways, by default it uses udp and multicast for cluster communication and discovery. If you prefer tcp and unicast further configuration is needed.
Finally in order to use the EJB remotely an application user is created (this username and password will be configured in the application).
./add-user.sh -a -u ejbuser -p ejbuser
At this point we have two wildfly instances in cluster (ha profile) running in the same machine.
Developing the EJB server
So now is the time to implement our little stateful EJB. The interface is very simple.
public interface RemoteCounter {
void increment();
void decrement();
int getCount();
String getJbossNode();
}
The maintained state is just a counter and the last method will be used to return the jboss.node.name property (which server is attending the call). The implementation is straight forward.
@Stateful
@Remote(RemoteCounter.class)
public class CounterBean implements RemoteCounter {
private int count = 0;
@Override
public void increment() {
this.count++;
}
@Override
public void decrement() {
this.count--;
}
@Override
public int getCount() {
return this.count;
}
@Override
public String getJbossNode() {
return System.getProperty("jboss.node.name");
}
}
Developing the client
This part is the interesting one, in a standalone java application (or using a non-wildfly app container) the configuration for the EJB is done using a jboss-ejb-client.properties which should be available in the classpath. This file defines the remote connections where the EJBs are going to be located. You can define more than one endpoint and the implementation will try to connect to them one by one. But, for a cluster setup, there is another vital part: the cluster configuration. When an EJB client connects to a server that is configured in cluster, it receives the cluster formation data (when a member in the cluster shuts down or when a member joins to the cluster). This way the client can provide clustering features (high availability, load balancing,...). The configuration that the client uses to connect to the cluster instances is the one that is defined in the cluster part of this file (I know the configuration is repeated multiple times but it was thought like this).
In summary, for my little setup the following jboss-ejb-client.properties is used:
remote.connections=ejbserver1,ejbserver2
remote.connection.ejbserver1.host=localhost
remote.connection.ejbserver1.port=8080
remote.connection.ejbserver1.protocol=http-remoting
remote.connection.ejbserver1.username=ejbuser
remote.connection.ejbserver1.password=ejbuser
remote.connection.ejbserver2.host=localhost
remote.connection.ejbserver2.port=18080
remote.connection.ejbserver2.protocol=http-remoting
remote.connection.ejbserver2.username=ejbuser
remote.connection.ejbserver2.password=ejbuser
remote.clusters=ejb
remote.cluster.ejb.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=true
remote.cluster.ejb.protocol=http-remoting
remote.cluster.ejb.username=ejbuser
remote.cluster.ejb.password=ejbuser
There are defined two connections (for each member of the cluster) and the configuration for the cluster (the connection data for each member of the cluster when members are received by the client). Think that you can omit one connection but then, if the only defined server is down when connecting, the client will fail although the second server is up and running (the cluster information is only used when the connection starts and the cluster formation information is received). Then the client is developed as shown in the jboss documentation.
InitialContext context = null;
try {
Properties jndiProperties = new Properties();
jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
context = new InitialContext(jndiProperties);
// lookup name is a bit tricky, check documentation
String lookupName = "ejb:/stateful-ejb-wildfly/CounterBean!es.rickyepoderi.samples.ejb.RemoteCounter?stateful";
RemoteCounter statefulRemoteCounter = (RemoteCounter) context.lookup(lookupName);
statefulRemoteCounter.increment();
// Use the stateful EJB
// ...
} finally {
if (context != null) {
try {
context.close();
} catch (Exception e) {
}
}
}
Demo
Here you have a simple video that shows the demo EJB application in action. The server ejb-server-1 is down at the beginning so, when the client is started, it fails to connect to the first connection but establishes it with the second server. After some increments in the second server the first one is started again. The client (as the server backend is clustered) receives that a new member is active. This way when the second server is shut down the stateful EJB can continue using the new discovered first server.
And that is all. Remember that there are more options to configure a remote EJB client but this is the good one. If, for whatever reason, you cannot use a fixed properties file there is a programmatic way of specifying the same configuration, although I have never used it. You can download the sample application I used for this entry from here.
Best regards!
Comments