JBoss.orgCommunity Documentation

Chapter 6. Clustered Session EJBs

6.1. Stateless Session Bean in EJB 3.0
6.2. Stateful Session Beans in EJB 3.0
6.2.1. The EJB application configuration
6.2.2. Optimize state replication
6.2.3. CacheManager service configuration
6.3. Stateless Session Bean in EJB 2.x
6.4. Stateful Session Bean in EJB 2.x
6.4.1. The EJB application configuration
6.4.2. Optimize state replication
6.4.3. The HASessionStateService configuration
6.4.4. Handling Cluster Restart
6.4.5. JNDI Lookup Process
6.4.6. SingleRetryInterceptor

Session EJBs provide remote invocation services. They are clustered based on the client-side interceptor architecture. The client application for a clustered session bean is the same as the client for the non-clustered version of the session bean, except for some minor changes. No code change or re-compilation is needed on the client side. Now, let's check out how to configure clustered session beans in EJB 3.0 and EJB 2.x server applications respectively.

Clustering stateless session beans is probably the easiest case since no state is involved. Calls can be load balanced to any participating node (i.e. any node that has this specific bean deployed) of the cluster.

To cluster a stateless session bean in EJB 3.0, simply annotate the bean class with the @Clustered annotation. This annotation contains optional parameters for overriding both the load balance policy and partition to use.

public @interface Clustered
{
   String partition() default "${jboss.partition.name:DefaultPartition}";
   String loadBalancePolicy() default "LoadBalancePolicy";
}
  • partition specifies the name of the cluster the bean participates in. While the @Clustered annotation lets you override the default partition, DefaultPartition, for an individual bean, you can override this for all beans using the jboss.partition.name system property.

  • loadBalancePolicy defines the name of a class implementing org.jboss.ha.client.loadbalance.LoadBalancePolicy, indicating how the bean stub should balance calls made on the nodes of the cluster. The default value, LoadBalancePolicy is a special token indicating the default policy for the session bean type. For stateless session beans, the default policy is org.jboss.ha.client.loadbalance.RoundRobin. You can override the default value using your own implementation, or choose one from the list of available policies:

    org.jboss.ha.client.loadbalance.RoundRobin

    Starting with a random target, always favors the next available target in the list, ensuring maximum load balancing always occurs.

    org.jboss.ha.client.loadbalance.RandomRobin

    Randomly selects its target without any consideration to previously selected targets.

    org.jboss.ha.client.loadbalance.aop.FirstAvailable

    Once a target is chosen, always favors that same target; i.e. no further load balancing occurs. Useful in cases where "sticky session" behavior is desired, e.g. stateful session beans.

    org.jboss.ha.client.loadbalance.aop.FirstAvailableIdenticalAllProxies

    Similar to FirstAvailable, except that the favored target is shared across all proxies.

Here is an example of a clustered EJB 3.0 stateless session bean implementation.

@Stateless
@Clustered
public class MyBean implements MySessionInt
{
   public void test()
   {
      // Do something cool
   }
}

Rather than using the @Clustered annotation, you can also enable clustering for a session bean in jboss.xml:

<jboss>    
   <enterprise-beans>
      <session>
         <ejb-name>NonAnnotationStateful</ejb-name>
         <clustered>true</clustered>
         <cluster-config>
            <partition-name>FooPartition</partition-name>
            <load-balance-policy>
               org.jboss.ha.framework.interfaces.RandomRobin
            </load-balance-policy>
         </cluster-config>
      </session>    
   </enterprise-beans>
</jboss>

Note

The <clustered>true</clustered> element is really just an alias for the <container-name>Clustered Stateless SessionBean</container-name> element in the conf/standardjboss.xml file.

In the bean configuration, only the <clustered> element is necessary to indicate that the bean needs to support clustering features. The default values for the optional <cluster-config> elements match those of the corresponding properties from the @Clustered annotation.

Clustering stateful session beans is more complex than clustering their stateless counterparts since JBoss needs to manage the state information. The state of all stateful session beans are replicated and synchronized across the cluster each time the state of a bean changes.

To cluster stateful session beans in EJB 3.0, you need to tag the bean implementation class with the @Clustered annotation, just as we did with the EJB 3.0 stateless session bean earlier. In contrast to stateless session beans, stateful session bean method invocations are load balanced using org.jboss.ha.client.loadbalance.aop.FirstAvailable policy, by default. Using this policy, methods invocations will stick to a randomly chosen node.

The @org.jboss.ejb3.annotation.CacheConfig annotation can also be applied to the bean to override the default caching behavior. Below is the definition of the @CacheConfig annotation:

public @interface CacheConfig
{
   String name() default "";
   int maxSize() default 10000;
   long idleTimeoutSeconds() default 300;   
   boolean replicationIsPassivation() default true;   
   long removalTimeoutSeconds() default 0;
}
  • name specifies the name of a cache configuration registered with the CacheManager service discussed in Section 6.2.3, “CacheManager service configuration”. By default, the sfsb-cache configuration will be used.

  • maxSize specifies the maximum number of beans that can cached before the cache should start passivating beans, using an LRU algorithm.

  • idleTimeoutSeconds specifies the max period of time a bean can go unused before the cache should passivate it (irregardless of whether maxSize beans are cached.)

  • removalTimeoutSeconds specifies the max period of time a bean can go unused before the cache should remove it altogether.

  • replicationIsPassivation specifies whether the cache should consider a replication as being equivalent to a passivation, and invoke any @PrePassivate and @PostActivate callbacks on the bean. By default true, since replication involves serializing the bean, and preparing for and recovering from serialization is a common reason for implementing the callback methods.

Here is an example of a clustered EJB 3.0 stateful session bean implementation.

@Stateful
@Clustered
@CacheConfig(maxSize=5000, removalTimeoutSeconds=18000)
public class MyBean implements MySessionInt
{
   private int state = 0;

   public void increment()
   {
      System.out.println("counter: " + (state++));
   }
}

As with stateless beans, the @Clustered annotation can alternatively be omitted and the clustering configuration instead applied to jboss.xml:

<jboss>    
   <enterprise-beans>
      <session>
         <ejb-name>NonAnnotationStateful</ejb-name>
         <clustered>true</clustered>
         <cache-config>
            <cache-max-size>5000</cache-max-size>
            <remove-timeout-seconds>18000</remove-timeout-seconds>
         </cache-config>
      </session>    
   </enterprise-beans>
</jboss>

JBoss Cache provides the session state replication service for EJB 3.0 stateful session beans. The CacheManager service, described in Section 3.2.1, “The JBoss AS CacheManager Service” is both a factory and registry of JBoss Cache instances. By default, stateful session beans use the sfsb-cache configuration from the CacheManager, defined as follows:

<bean name="StandardSFSBCacheConfig" class="org.jboss.cache.config.Configuration">

  <!--  No transaction manager lookup -->
  
  <!-- Name of cluster. Needs to be the same for all members -->
  <property name="clusterName">
    ${jboss.partition.name:DefaultPartition}-SFSBCache
  </property>
  <!--
    Use a UDP (multicast) based stack. Need JGroups flow control (FC)
    because we are using asynchronous replication.
  -->
  <property name="multiplexerStack">${jboss.default.jgroups.stack:udp}</property>
  <property name="fetchInMemoryState">true</property>
  
  <property name="nodeLockingScheme">PESSIMISTIC</property>
  <property name="isolationLevel">REPEATABLE_READ</property>
  <property name="useLockStriping">false</property>
  <property name="cacheMode">REPL_ASYNC</property>
  
  <!--
    Number of milliseconds to wait until all responses for a
    synchronous call have been received. Make this longer 
    than lockAcquisitionTimeout.
  -->
  <property name="syncReplTimeout">17500</property>
  <!-- Max number of milliseconds to wait for a lock acquisition -->
  <property name="lockAcquisitionTimeout">15000</property>
  <!-- The max amount of time (in milliseconds) we wait until the
  state (ie. the contents of the cache) are retrieved from
  existing members at startup. -->
  <property name="stateRetrievalTimeout">60000</property>
  
  <!--
    SFSBs use region-based marshalling to provide for partial state
    transfer during deployment/undeployment.
  -->
  <property name="useRegionBasedMarshalling">false</property>
  <!-- Must match the value of "useRegionBasedMarshalling" -->
  <property name="inactiveOnStartup">false</property>
  
  <!-- Disable asynchronous RPC marshalling/sending -->
  <property name="serializationExecutorPoolSize">0</property>        
  <!-- We have no asynchronous notification listeners -->
  <property name="listenerAsyncPoolSize">0</property>
  
  <property name="exposeManagementStatistics">true</property>
  
  <property name="buddyReplicationConfig">
    <bean class="org.jboss.cache.config.BuddyReplicationConfig">
    
      <!--  Just set to true to turn on buddy replication -->
      <property name="enabled">false</property>
      
      <!--
        A way to specify a preferred replication group.  We try
        and pick a buddy who shares the same pool name (falling 
        back to other buddies if not available).
      -->
      <property name="buddyPoolName">default</property>
      
      <property name="buddyCommunicationTimeout">17500</property>
      
      <!-- Do not change these -->
      <property name="autoDataGravitation">false</property>
      <property name="dataGravitationRemoveOnFind">true</property>
      <property name="dataGravitationSearchBackupTrees">true</property>
               
      <property name="buddyLocatorConfig">
        <bean class="org.jboss.cache.buddyreplication.NextMemberBuddyLocatorConfig">
          <!-- The number of backup nodes we maintain -->
          <property name="numBuddies">1</property>
          <!-- Means that each node will *try* to select a buddy on 
               a different physical host. If not able to do so 
               though, it will fall back to colocated nodes. -->
          <property name="ignoreColocatedBuddies">true</property>
        </bean>
      </property>
    </bean>
  </property>
  <property name="cacheLoaderConfig">
    <bean class="org.jboss.cache.config.CacheLoaderConfig">
      <!-- Do not change these -->
      <property name="passivation">true</property>
      <property name="shared">false</property>
      
      <property name="individualCacheLoaderConfigs">
        <list>
          <bean class="org.jboss.cache.loader.FileCacheLoaderConfig">
            <!-- Where passivated sessions are stored -->
            <property name="location">${jboss.server.data.dir}${/}sfsb</property>
            <!-- Do not change these -->
            <property name="async">false</property>
            <property name="fetchPersistentState">true</property>
            <property name="purgeOnStartup">true</property>
            <property name="ignoreModifications">false</property>
            <property name="checkCharacterPortability">false</property>
          </bean>
        </list>
      </property>
    </bean>
  </property>

  <!-- EJBs use JBoss Cache eviction -->
  <property name="evictionConfig">
    <bean class="org.jboss.cache.config.EvictionConfig">
      <property name="wakeupInterval">5000</property>
      <!--  Overall default -->
      <property name="defaultEvictionRegionConfig">
        <bean class="org.jboss.cache.config.EvictionRegionConfig">
          <property name="regionName">/</property>
          <property name="evictionAlgorithmConfig">
            <bean class="org.jboss.cache.eviction.NullEvictionAlgorithmConfig"/>
          </property>
        </bean>
      </property>
      <!-- EJB3 integration code will programatically create other regions as beans are deployed -->
    </bean>
  </property>
</bean>

To make an EJB 2.x bean clustered, you need to modify its jboss.xml descriptor to contain a <clustered> tag.

<jboss>
  <enterprise-beans>
    <session>
      <ejb-name>nextgen.StatelessSession</ejb-name>
      <jndi-name>nextgen.StatelessSession</jndi-name>
      <clustered>true</clustered>
      <cluster-config>
        <partition-name>DefaultPartition</partition-name>
        <home-load-balance-policy>
           org.jboss.ha.framework.interfaces.RoundRobin
        </home-load-balance-policy>
        <bean-load-balance-policy>
           org.jboss.ha.framework.interfaces.RoundRobin
        </bean-load-balance-policy>
      </cluster-config>
    </session>
  </enterprise-beans>
</jboss>

Clustering stateful session beans is more complex than clustering their stateless counterparts since JBoss needs to manage the state information. The state of all stateful session beans are replicated and synchronized across the cluster each time the state of a bean changes. The JBoss AS uses the HASessionStateService bean to manage distributed session states for clustered EJB 2.x stateful session beans. In this section, we cover both the session bean configuration and the HASessionStateService bean configuration.

The HASessionStateService bean is defined in the all/deploy/cluster/ha-legacy-jboss-beans.xml file.

<bean name="HASessionStateService"
      class="org.jboss.ha.hasessionstate.server.HASessionStateService">
  
  <annotation>@org.jboss.aop.microcontainer.aspects.jmx.JMX(...)</annotation>
  
  <!--  Partition used for group RPCs -->
  <property name="HAPartition"><inject bean="HAPartition"/></property>
  
  <!-- JNDI name under which the service is bound -->
  <property name="jndiName">/HASessionState/Default</property>
  <!-- Max delay before cleaning unreclaimed state.
       Defaults to 30*60*1000 => 30 minutes -->
  <property name="beanCleaningDelay">0</property>
   
</bean>

The configuration attributes in the HASessionStateService bean are listed below.

We have covered the HA smart client architecture in the section called “Client-side interceptor architecture”. The default HA smart proxy client can only failover as long as one node in the cluster exists. If there is a complete cluster shutdown, the proxy becomes orphaned and loses knowledge of the available nodes in the cluster. There is no way for the proxy to recover from this. The proxy needs to look up a fresh set of targets out of JNDI/HAJNDI when the nodes are restarted.

The 3.2.7+/4.0.2+ releases contain a RetryInterceptor that can be added to the proxy client side interceptor stack to allow for a transparent recovery from such a restart failure. To enable it for an EJB, setup an invoker-proxy-binding that includes the RetryInterceptor. Below is an example jboss.xml configuration.

<jboss>
  <session>
    <ejb-name>nextgen_RetryInterceptorStatelessSession</ejb-name>
    <invoker-bindings>
      <invoker>
        <invoker-proxy-binding-name>
           clustered-retry-stateless-rmi-invoker
        </invoker-proxy-binding-name>
        <jndi-name>nextgen_RetryInterceptorStatelessSession</jndi-name>
      </invoker>
    </invoker-bindings>
    <clustered>true</clustered>
  </session>
  <invoker-proxy-binding>
    <name>clustered-retry-stateless-rmi-invoker</name>
    <invoker-mbean>jboss:service=invoker,type=jrmpha</invoker-mbean>
    <proxy-factory>org.jboss.proxy.ejb.ProxyFactoryHA</proxy-factory>
    <proxy-factory-config>
      <client-interceptors>
        <home>
          <interceptor>org.jboss.proxy.ejb.HomeInterceptor</interceptor>
          <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
          <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor>
          <interceptor>org.jboss.proxy.ejb.RetryInterceptor</interceptor>
          <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
        </home>
        <bean>
          <interceptor>org.jboss.proxy.ejb.StatelessSessionInterceptor</interceptor>
          <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
          <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor>
          <interceptor>org.jboss.proxy.ejb.RetryInterceptor</interceptor>
          <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
        </bean>
      </client-interceptors>
    </proxy-factory-config>
  </invoker-proxy-binding>
</jboss>

In order to recover the HA proxy, the RetryInterceptor does a lookup in JNDI. This means that internally it creates a new InitialContext and does a JNDI lookup. But, for that lookup to succeed, the InitialContext needs to be configured properly to find your naming server. The RetryInterceptor will go through the following steps in attempting to determine the proper naming environment properties: