Dispatching commands on WildFly cluster

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

wildfly cluster execute command

 

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.

wildfly cluster tutorial execute command

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

Found the article helpful? if so please follow us on Socials