Magellan - A clustering tutorial with WildFly, nginx, Scala(FX) and Gradle

Introduction

High availability is an essential requirement for more and more websites:

From our WildFly-oriented point of view, a cluster can be defined as a set of server instances (nodes) that behave as a single network component serving requests; it effectively addresses the above issues:

In such a scenario, load balancing is a complementary tool, creating a sort of network facade making clustering totally transparent to clients - as they actually refer to a well-defined access point.

This tutorial will guide us step by step - without any claim of completeness - through the setup of a cluster and the deployment and execution of Magellan, a simple, open source Java EE application consisting of:

While playing with clustering, we’ll deal with a wide range of modern technologies:

Since we are working in the Java ecosystem, any OS supporting Java can be employed while following the suggested steps.

Standalone and domain clustering

With regard to clustering, WildFly supports two distinct operating modes:

Despite the fundamental differences, both clustering modes support state replication; therefore, for the sake of simplicity, in this article we’ll learn how to create a cluster of two standalone servers on the same machine - but several ideas and concepts apply to domains as well.

Creating a standalone cluster

First of all, we’ll start two different instances (nodes) of WildFly:

  1. Download the latest stable version of WildFly from its download page and extract the zip twice, creating two identical directory trees - let’s assume they are named wildfly-alpha and wildfly-beta

  2. In a terminal, cd to wildfly-alpha/bin and run the command:

    ./standalone.sh -c standalone-ha.xml -u 230.0.0.4 -Djboss.node.name=alpha -Djboss.socket.binding.port-offset=1
    

    Where:

    • -u declares the multicast address shared by all the server instances in the cluster

    • -Djboss.node.name introduces a node name; every server instance must have its unique node name

    • -Djboss.socket.binding.port-offset adds the given offset to every default port number defined by the server instance - especially convenient when starting multiple nodes on the same machine

  3. In another terminal (or, better, another terminal tab), cd to wildfly-beta/bin and run:

    ./standalone.sh -c standalone-ha.xml -u 230.0.0.4 -Djboss.node.name=beta -Djboss.socket.binding.port-offset=2
    

    Compared to the previous server invocation:

    • The multicast address is the very same

    • The node name and the port offset are different

The cluster is ready! To test its capabilities, we are going to deploy Magellan, our Java EE application.

Deploying Magellan

  1. Download the .ear archive from the latest release page

  2. Copy it to the standalone/deployments subdirectory, for both WildFly instances

Incidentally, please consider that Magellan is a standard Java Enterprise application, so it could be deployed to other Java EE servers, such as GlassFish, to study high availability.

Testing HTTP session replication

  1. Point your web browser to the following addresses:

  2. By refreshing each page, you’ll see an increment of the very same counter (stored in the replicated HTTP session)

The session-based servlet

You might wonder what special classes are employed by the servlet to achieve session replication: luckily, the answer is none - clustering is completely transparent to the source code of Java EE components.

On the other hand, your web application must include the <distributable> tag in its web.xml descriptor: were it missing, both pages would see their counter reset to 0 whenever you switch from one page to the other!

Reference files

Load balancing with nginx

While verifying that our virtual nodes compose a cluster, we noticed that contacting either of them is equivalent; however, an inconvenience arises: we always need to know their network address. Not only: should a node crash, we’d have to remember the address of the other node - and what if our cluster includes thousands of nodes?

Load balancing is the natural solution to the problem; a load balancer is a network component that:

In other terms, a load balancer is fairly similar to the Facade design pattern in OOP sofware engineering.

There are several kinds of load balancers:

What’s more, a few solutions also provide replication, not to make the load balancer a single point of failure - that is, if one of the load balancer components crashes, the balancer as a whole continues serving at the given address.

In this tutorial, we’ll employ nginx - an elegant, minimalist and fast server, providing fine-grained load balancing with just a few lines of configuration; another idea could be mod_cluster, a module for Apache HTTP server dedicated to WildFly.

The steps are quite straightforward:

  1. Install nginx. It should be available as a binary package for most operating systems. On Linux, in particular, you can also:

    1. Download and extract the source code archive

    2. Open a terminal and cd to the extracted directory

    3. Run the usual set of commands:

      ./configure --prefix="<where you want to install nginx, for example $HOME/nginx>"
      
      make
      
      make install
      
    4. Add its sbin directory to your PATH environment variable

  2. Download the configuration file for our cluster topology, nginx-balancer.conf, from Magellan’s release files

  3. Run:

    nginx -c "<path to the configuration file>"
    
  4. Navigate to http://localhost:8080/magellan and refresh the page a few times

  5. Turn off either WildFly server, and notice that the web application runs unaffected after refreshing the page

  6. Restart that instance and shut down the other - further page refreshes will continue showing increased values for the counter.

Creating a stateful session bean

Stateful session beans are transparently replicated; should you need to deploy them to less recent server versions, you can mark them via the @Clustered annotation and/or specific tags in the jboss-ejb3.xml descriptor.

For consistency, we’ll expand the “counter” example by implementing a basic EJB counter, in magellan-ejb.

Reference files

Connecting to the EJB from the web tier

Now, we want to create a servlet showing the value of our EJB counter for the current HTTP session.

Here comes the tricky part; one might think to:

Unfortunately, neither of the above methods will work correctly.

The 2 simplest solutions are perhaps:

  1. Employ a proxy CDI bean:

    1. Create a @SessionScoped CDI bean in the web project - named, for example, CounterClient

    2. Inject the counter EJB into it - via @Inject or @EJB - in order to keep the reference to the counter EJB for the life of the HTTP session

    3. Inject CounterClient (via @Inject) into the servlet

    The advantages of such approach are:

    • you do not need to perform explicit lookups if the web tier is in the same ear as the EJB tier - thus supporting local injection

    • the client bean can add logic to simplify the interface of the wrapped EJB - or it can just expose the original bean via a getter, or introduce a mixed approach

    • finally, by using @Named, you can easily integrate it into other technologies, such as Java Server Faces.

  2. Call HttpSession’s getAttribute() method to retrieve the EJB handle, looking it up via InitialContext only if it was not found. A drawback is that such approach would require an explicit naming operation even for resolving a local interface.

Reference files

Testing the web-EJB connection

Once again, we can employ our load balancer, and run:

By alternately shutting down the server instances like we did for the session servlet, we can test state replication for our bean.

The ScalaFX client

What if we want to access a stateful EJB from a rich UI client? In this example, a ScalaFX app will call the methods exposed by the counter EJB - with each execution of the app referencing a different bean instance.

ScalaFX is a simple and elegant library providing Scala bindings and new syntax constructs for the modern JavaFX GUI toolkit.

The suggested way to run the example is MoonDeploy, an open source deployment tool similar to Java Web Start: if MoonDeploy is installed, just click on App.moondeploy in Magellan’s latest release file list and open it with MoonDeploy - the application will be downloaded and started.

By clicking on the two buttons shown in the JavaFX user interface, you’ll actually interact with the session bean on the server.

Highlights of the ScalaFX client

The core of the program is the interaction with the stateful EJB - which is not a standard, portable process; in the case of WildFly, a few basic steps are required:

  1. Reference org.wildfly:wildfly-ejb-client-bom:WILDFLY_VERSION as a dependency in the Gradle build script - where WILDFLY_VERSION could be, for example, 9.0.2.Final

  2. Reference the client interface library provided by the EJB developer - in this case, as the Gradle projects share the same root project, we just employed project(":magellan-common")

  3. Perform an almost standard JNDI lookup:

    1. Instantiate an InitialContext with no constructor parameters

    2. Call its lookup() method, passing the portable JNDI name without the java:global/ prefix, then casting the result to the remote interface; in the example, the reduced JNDI name is:

      Magellan/magellan-ejb/CounterBean!info.gianlucacosta.clustering.magellan.ejb.CounterRemote

      whose components are:

      • App name: Magellan
      • Module name: magellan-ejb
      • Bean name: CounterBean
      • FQN of the referenced interface: info.gianlucacosta.clustering.magellan.ejb.CounterRemote
    3. Just call the methods of the remote interface, as if it were a local object

  4. Create a jndi.properties file in src/main/resources, required to correctly instantiate the InitialContext in the above steps; alternatively, you might choose to pass a Hashtable to InitialContext’s constructor, especially in case of dynamic parameters.

  5. jndi.properties must include at least the following lines:

    • java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory to declare the provider employed by JNDI so as to connect to the server

    • java.naming.provider.url=http-remoting://localhost:8081,http-remoting://localhost:8082 a comma-separated list of nodes in the cluster used for connecting to the cluster. We are passing the explicit list of addresses because our load balancer handles HTTP interactions - which is not the case of the rich client

    • jboss.naming.client.ejb.context=true mandatory when you perform an EJB lookup

Reference files

Conclusion

Well, it seems we have covered quite a bit of ground! ^__^ I really hope you have found this brief tutorial a helpful starting point for learning more about clustering, load balancing and the world of Java EE in general - especially with WildFly, Gradle and the Scala programming language! ^__^

Further references

Tags:

EJB Gradle JNDI MoonDeploy Scala ScalaFX WildFly clustering networking nginx tutorial