8 Replies Latest reply on Apr 15, 2014 1:27 AM by vidya.sagar

    Accessing HA-Singletons in AS 7

    tory.mckeag

      Hi, I'm porting an app from JBoss 5 to 7.  I have an HA singleton EJB (annotated using org.jboss.ejb3.annotation.Service in JBoss 5) that I was able to access before from any node in the cluster.  Essentially, I would be able to call the EJB and it would only be invoked on the master node at any given time, by looking it up in HA-JNDI, like this:

       

          private static final Properties p = new Properties();
          static {
               // The partition name may be overridden via system properties.  
               String partitionName = System.getProperty("jboss.partition.name");
               if(partitionName == null) {
                      partitionName = "DefaultPartition";
               }
          
              p.put(Context.INITIAL_CONTEXT_FACTORY,
                    "org.jnp.interfaces.NamingContextFactory");
              p.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
              p.put("jnp.partitionName", partitionName);
          }
          
          public static Object lookupSingleton(String name) throws NamingException {
                    Context ctx = new InitialContext(p);
                    Object result = ctx.lookup(name);
              return result;
          }
      

      Now, I've read the example I found in the EAP documentation here: https://access.redhat.com/knowledge/docs/en-US/JBoss_Enterprise_Application_Platform/6/html-single/Development_Guide/index.html#TopicID9154

       

      I can make that work, and that's cool- part of the problem just to make it start and stop just on one node.  But I still don't know how to access it.  The example is unfortunately trivial; passing a string from the singleton isn't very interesting.  What I want is for any node on the cluster to be able to call various methods on the service. 

       

      How do you do this in JBoss 7?  I tried using an EJB reference for the returned result of getValue(), but that just seems to call the EJB in the local JVM after it's returned.  Is there a way I can return the node name (as the sample does), and use the EJB Client API to get a reference to an EJB on the master node? 

       

      Help?

        • 1. Re: Accessing HA-Singletons in AS 7
          carsten.metzler

          I have exact the same problem. Is there any solution?

          • 2. Re: Accessing HA-Singletons in AS 7
            bmaxwell

            There is a quickstart that does this - https://github.com/jboss-jdf/jboss-as-quickstart/tree/master/cluster-ha-singleton

             

            And don't confuse the MSC code which is used to determine which node in the cluster is the master with the Singleton code.  The MSC code is used to determine which node is the master by sending the node name.  Your singleton code which will be an ejb has what ever methods you want on it.

            • 3. Re: Accessing HA-Singletons in AS 7
              carsten.metzler

              The "singleton" in the quickstart ist the SchedulerBean. But this is installed on each node in the cluster, initialized only on the master node (by the HATimerService). But on each node a JNDI lookup is possible. So if my application looks up the scheduler maybe it gets the one on the slave node which has not been initialized.

              • 4. Re: Accessing HA-Singletons in AS 7
                wdfink

                Could you show your what you do and explain what you try to achieve?

                • 5. Re: Re: Accessing HA-Singletons in AS 7
                  carsten.metzler
                  I want to create a clusterwide singleton service implementing the following interface (just an example):
                  public interface DummyService extends Serializable {
                  
                      public void suspend();
                      public void resume();
                      public void execute();
                      public List<?> getTasks();
                  }
                  

                   

                  The implementing class ist the DummyServiceBean.java:

                   

                  @Singleton
                  public class DummyServiceBean implements DummyService {
                  
                      @Override
                      public void suspend() {
                          // suspend the service
                      }
                  
                      @Override
                      public void resume() {
                          // resume the service
                      }
                  
                      @Override
                      public void execute() {
                          // execute the service, i.e. reading a database table with tasks to execute
                      }
                  
                      @Override
                      public List<?> getTasks() {
                          Vector<String> tasks = new Vector<>();
                          // read tasks from database ...
                          return tasks;
                      }
                  }
                  
                  
                  
                  
                  

                   

                  The execute()-method is periodically called by a timer and looks for tasks to execute in a database table. By suspend() the service could be stopped from doing that. It is called from the business logic.

                   

                  I wrote a DummyServiceController that implements the MSC-Service-interface:

                   

                  public class DummyServiceController implements Service<DummyService> {
                  
                      public final InjectedValue<ServerEnvironment> env = new InjectedValue<ServerEnvironment>();
                      public static final ServiceName SERVICE_NAME = ServiceName.JBOSS.append("service", "DummyService");
                  
                      DummyService serviceBean;
                  
                      @Override
                      public DummyService getValue() throws IllegalStateException, IllegalArgumentException {
                          // return Reference to Service-EJB
                          return serviceBean;
                      }
                  
                      @Override
                      public void start(StartContext context) throws StartException {
                          // ...
                  
                          try {
                              InitialContext ic = new InitialContext();
                              serviceBean = (DummyService)ic.lookup("java:global/DummyServiceBean!services.DummyService");
                          }
                          catch(Exception ex) {
                  
                          }
                      }
                  
                      @Override
                      public void stop(StopContext context) {
                          // ...
                      }
                  }
                  
                  
                  
                  
                  
                  
                  
                  

                   

                  This controller holds an EJB-reference to the service bean and returns it by the getValue()-method.

                   

                  The controller ist started by the DummyServiceActivator:

                   

                  public class DummyServiceActivator implements ServiceActivator {
                  
                      @Override
                      public void activate(ServiceActivatorContext context) throws ServiceRegistryException {
                  
                          DummyServiceController ctrl = new DummyServiceController();
                  
                          SingletonService<DummyService> singleton = new SingletonService<DummyService>(ctrl, DummyServiceController.SERVICE_NAME);
                          singleton.build(context.getServiceTarget())
                              .addDependency(ServerEnvironmentService.SERVICE_NAME, ServerEnvironment.class, ctrl.env)
                              .setInitialMode(ServiceController.Mode.ACTIVE)
                              .install();
                      }
                  }
                  
                  
                  

                   

                  The service is accessed via the DummyServiceAccessBean:

                   

                  @Stateless
                  public class DummyServiceAccessBean implements DummyService {
                  
                      @Override
                      public void suspend() {
                          // call singleton
                          getSingleton().suspend();
                      }
                  
                      @Override
                      public void resume() {
                          // call singleton
                          getSingleton().resume();
                      }
                  
                      @Override
                      public void execute() {
                          // call singleton
                          getSingleton().execute();
                      }
                  
                      @Override
                      public List<?> getTasks() {
                          // call singleton
                          return getSingleton().getTasks();
                      }
                  
                      private DummyService getSingleton() {
                  
                          return (DummyService)CurrentServiceContainer.getServiceContainer().getService(
                                  DummyServiceController.SERVICE_NAME).getValue();
                      }
                  }
                  
                  
                  
                  
                  
                  
                  

                   

                  This access bean, available on each clusternode, forwards the requests to the singleton service running only on the masternode.

                   

                  That was my approach - but i realized that the service bean is executed on the node where the calls come from and not on the masternode. So my question: How can i create a singleton service (implementing a custom interface) that is accessable for the business application from each cluster node?

                  • 6. Re: Re: Re: Accessing HA-Singletons in AS 7
                    vidya.sagar

                    Hello,

                     

                    Do not get confused with the MSC code, it is used to determine which node in the cluster is the master with the Singleton code.

                    You should also have a singleton EJB, which should be invoked remotely based on the node-name (received by SingletonService).

                    And then invoke any method available within the EJB.

                     

                    Let’s follow the below steps:

                    1. Created a singleton service to return node-name of the server where Service is actively running (Jboss server should definitely be running with unique node-name)

                    2. Created one Remote Singleton Session Bean which contains several methods as per business needs.

                    3. Based on node-name (received from SingletonService), do a lookup for the EJB and then call respective methods via remote interface.

                        The remote EJB invocation should be based on "EJB client API approach" way, otherwise the remote invocation will intern be converted into local invocation.

                     

                    "EJB client API approach" for remote EJB invocation from one node to another node of clustered JBOSS:

                    -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

                    1. To call EJB from remote location we need to enable "remoting-ejb-receiver" on server side.

                        Please refer to “standalone_changes.xml” to know change details.

                    2. Also we need to register the "remoting-ejb-receiver" to the application, so that the application can receive remote EJB.

                        Please refer to “jboss-ejb-client.xml” section

                    3. Now we need to call the remote EJB in "EJB client API approach" way, which needs to have JNDI name pattern as:

                      ejb:<app-name>/<module-name>/<distinct-name>/<bean-name>!<fullclassname-of-the-remote-interface>

                      In our case it will be: ejb:dummy/common/node1/DummyServiceImpl!test.startup.DummyServiceRemote

                      Important to note that identification to remote location IP address is not based on InitialContext as InitialContext will note contain any IP address as normally happens with "remote://URL:Port".

                      The remote location identification is based on <distinct-name> passed in JNDI. JBOSS will internally identify the remote IP based on <distinct-name>.

                      Hence is required to provide <distinct-name> to the application running on different nodes.

                      Add "<distinct-name>${jboss.node.name}</distinct-name>" to “jboss-app.xml”. Make sure that jboss.node.name property is always unique.

                     

                     

                    Refer to:

                    "https://docs.jboss.org/author/display/AS71/Remote+EJB+invocations+via+JNDI+-+EJB+client+API+or+remote-naming+project" which tells us different way of remote EJB location.

                    standalone_changes.xml

                    jboss-app.xml

                    jboss-ejb-client.xml

                    • 7. Re: Accessing HA-Singletons in AS 7
                      wdfink

                      From my perspective this approach will be error-prone.

                      Using the distinct name will be difficult as you need to change the application jboss-app.xml for each server, also you might deploy an application to the wrong server and you will invoke node1 but reach nodeX where the distinct name is node1 by accident.

                       

                      Maybe a better approach is to use the scoped-context and set the node-name to somthing that can be resolved as host-name and use it as is.

                      Second thing is that the port 4447 might be changed and you need to code that in the node name i.e. host5.4547 and parse it for the invocation.

                       

                      But as I said it is still a hack and error-prone as I think there are more issues.

                      • 8. Re: Accessing HA-Singletons in AS 7
                        vidya.sagar

                        Hello ,

                         

                        The distinct-name in jboss-app.xml is not static and hence no need to change as per server.

                        ${jboss.node.name} is an environment variable which can be defined while starting the node (startDummy.sh/startDummy.bat).

                        The jboss.node.name variable will be a placeholder which will hold the value based on environment variable value.

                         

                         

                        Also there might be chance of error while invoking remote EJB, if the remote server goes down suddenly.

                        This can be handled by applying fallback mechanism.

                        ie, in case of exception (IllegalStateException , EJBAccessException) while invoking remote EJB, we will apply waitAndGetService.

                        waitAndGetService can have logic to retry (ex 5 attempts) to check if service is available, if not system will throw exception.

                         

                        Applying fallback mechanism will make application error free & will be able to serve even in case of downtime.