This tutorial has been updated, thanks to the suggestions received on developer.jboss.org. We thank our readers for reporting any issue found in our tutorials.
This is the second article about WildFly clustering API. You can read here the first tutorial: Monitoring a cluster using WildFly API. In this article we will learn how to execute commands on Node/s of the cluster using WildFly’s Dispatch API
The Dispatcher is a standard Java pattern used to group the execution of commands to a component named, in fact the dispatcher. In WildFly terms the Dispatcher is org.wildfly.clustering.dispatcher.CommandDispatcher
The CommandDispatcher can be used in conjunction with the org.wildfly.clustering.dispatcher.CommandDispatcherFactory. which can be used, as you can imagine, to create a CommandDispatcher.
The advantage of using this API is that you can direct the execution of Commands to all Nodes of a Cluster of to a single Node of the cluster.
Let’s see it in practice. At first you need a CommandDispatcherFactory which will be in charge to create a CommandDispatcher and a Bean which is triggered when the application is deployed, and will make available the CommandDispatcher in the Server acting as a glue between the applications and the CommandDispatcher.
Let’s see at first our Factory Bean:
package com.sample; import javax.annotation.Resource; import javax.ejb.Singleton; import javax.ejb.Startup; import org.wildfly.clustering.dispatcher.CommandDispatcher; import org.wildfly.clustering.dispatcher.CommandDispatcherFactory; import org.wildfly.clustering.group.Group; @Singleton @Startup public class CommandDispatcherFactoryBean implements CommandDispatcherFactory { @Resource(lookup = "java:jboss/clustering/dispatcher/web") private CommandDispatcherFactory factory; public <C> CommandDispatcher<C> createCommandDispatcher(Object service, C context) { return this.factory.createCommandDispatcher(service, context); } @Override public Group getGroup() { return this.factory.getGroup(); } }
As you can see, the above code is a candidate for a web application, since it uses the “web” cluster channel. Now the CommandDispatcherBean, which is also a SingletonBean, and it’s in charge to init the CommandDispatcher after deployment:
package com.sample; import java.util.Map; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.ejb.EJB; import javax.ejb.Singleton; import javax.ejb.Startup; import org.wildfly.clustering.dispatcher.Command; import org.wildfly.clustering.dispatcher.CommandDispatcher; import org.wildfly.clustering.dispatcher.CommandDispatcherFactory; import org.wildfly.clustering.dispatcher.CommandResponse; import org.wildfly.clustering.group.Node; @Singleton @Startup public class CommandDispatcherBean { @EJB private CommandDispatcherFactory factory; private CommandDispatcher<Node> dispatcher; @PostConstruct public void init() { this.dispatcher = this.factory.createCommandDispatcher("CommandDispatcher", this.factory.getGroup().getLocalNode()); } @PreDestroy public void destroy() { this.close(); } public <R> CommandResponse<R> executeOnNode(Command<R, Node> command, Node node) throws Exception { return this.dispatcher.executeOnNode(command, node); } public <R> Map<Node, CommandResponse<R>> executeOnCluster(Command<R, Node> command, Node... excludedNodes) throws Exception { return this.dispatcher.executeOnCluster(command, excludedNodes); } public void close() { this.dispatcher.close(); } }
To complete our example, we will implement a org.wildfly.clustering.dispatcher.Command with the following Class GarbageCollectorCommand, that executes a Garbage collector on the JVM:
package com.sample; import org.wildfly.clustering.dispatcher.Command; import org.wildfly.clustering.group.Node; public class GarbageCollectorCommand implements Command<String, Node> { @Override public String execute(Node node) { System.gc(); return node.getName(); } }
Running the Command on a Node
The simplest case is when we execute the Dispatcher’s executeOnNode command which can direct the execution of a single node of the cluster. You can imagine several scenarios for it such as CPU intensive operations which should be executed on one Node of the cluster which has the best available hardware. The following Servlet will execute the Command on the first Node registered in the cluster:
package com.sample; import java.io.IOException; import java.io.PrintWriter; import java.util.logging.*; import javax.annotation.Resource; import javax.ejb.EJB; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import org.wildfly.clustering.dispatcher.Command; import org.wildfly.clustering.dispatcher.CommandDispatcherFactory; import org.wildfly.clustering.dispatcher.CommandResponse; import org.wildfly.clustering.group.Group; import org.wildfly.clustering.group.Node; @WebServlet(name = "TestCommandNode", urlPatterns = {"/TestCommandNode"}) public class TestCommandNode extends HttpServlet { @Resource(lookup = "java:jboss/clustering/group/web") private Group channelGroup; @Resource(lookup = "java:jboss/clustering/dispatcher/web") private CommandDispatcherFactory factory; private final Command<String, Node> command = new GarbageCollectorCommand(); @EJB CommandDispatcherBean ejb; protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); try { // Execute command on the first Node Node node = channelGroup.getNodes().get(0); CommandResponse cr = ejb.executeOnNode(command, node); out.println(cr + " executed on " + node.getName()); } catch (Exception ex) { Logger.getLogger(TestCommandCluster.class.getName()).log(Level.SEVERE, null, ex); } out.close(); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } }
Running the Command on all the cluster
Running the Command on all cluster nodes is not much more complicated: you will execute the executeOnCluster method, passing as argument the Command and the list of excluded Nodes (in our case none):
package com.sample; import java.io.IOException; import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Resource; import javax.ejb.EJB; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import org.wildfly.clustering.dispatcher.Command; import org.wildfly.clustering.dispatcher.CommandDispatcherFactory; import org.wildfly.clustering.dispatcher.CommandResponse; import org.wildfly.clustering.group.Group; import org.wildfly.clustering.group.Node; @WebServlet(name = "TestCommandCluster", urlPatterns = {"/TestCommandCluster"}) public class TestCommandCluster extends HttpServlet { @Resource(lookup = "java:jboss/clustering/group/web") private Group channelGroup; @Resource(lookup = "java:jboss/clustering/dispatcher/web") private CommandDispatcherFactory factory; private final Command<String, Node> command = new GarbageCollectorCommand(); @EJB CommandDispatcherBean ejb; protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); try { List<Node> nodes = channelGroup.getNodes(); Map<Node, CommandResponse<String>> map = ejb.executeOnCluster(command, null); for (Map.Entry<Node, CommandResponse<String>> entry : map.entrySet()) { out.println(entry.getValue() + " executed on " + entry.getKey()); } } catch (Exception ex) { Logger.getLogger(TestCommandCluster.class.getName()).log(Level.SEVERE, null, ex); } out.close(); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } }
Deploying the application
Before deploying the application, you will need to activate the org.wildfly.clustering.api dependencies. You can do it through the jboss-deployment-structure.xml file as follows:
<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.2"> <deployment> <dependencies> <module name="org.wildfly.clustering.api" services="export"/> </dependencies> </deployment> </jboss-deployment-structure>
References: https://developer.jboss.org/wiki/ClusteringChangesInWildFly8?_sscc=t
The source code for the NetBean project is available at: https://github.com/fmarchioni/mastertheboss/tree/master/cluster-dispatcher