TransactionStickyLoadBalancePolicies

Version 16

    Introduction

    First of all, what is transaction sticky functionality about? The aim is to:

    • Enable both remote user transaction and EJB invocations within a user transaction to hit the same server for the duration of the transaction.
    • Once the transaction has finished, a new one is started, a new node can be chosen or not, depending on the policy used, but again, for the duration of that transaction, all invocations hit the same server.
    • If a failure is detected during a transaction and the transaction was already started in the server, no failover is allowed and exception is propagated back to client. For example, if a sticky node (node that was serving a transaction) is shutdown in the middle of a transaction, failover cannot happen.
    • If a failure is detected before the transaction was started in the server, failover is allowed and a new sticky node can be chosen. This can happen for example if UserTransaction.begin() could not succeed because the target node has been shutdown.
    • Transaction sticky functionality only makes sense in homogeneous deployment scenarios, where all remote EJBs to be called within a user transaction are homogeneously deployed accross the cluster. As indicated in this forum entry, if a node does not contain one of the EJBs to be called within a transaction, when the transaction hits that server node, the transaction will fail because the EJB is missing and this is something that it cannot be predicted at the start of the transaction.
    • As a result of this, there can be situations, specially during AS/EAP startup, when a remote client starts a user transaction but all the EJBs to be called remotely within the transaction have not yet been fully deployed. In this case, the transaction would fail when a missing remote EJB was encountered. Again, when the transaction is started on the server side, it cannot be predicted which EJBs will be called remotely and it cannot be checked that they're deployed.

     

    Although transaction sticky load balance policies were originally developed as part of JBAS-4455 / JBPAPP-546 and they're optionally available for clustered EJB2 beans ever since, they won't be fully functional until AS 5.0.0.GA, EAP 4.2.0.GA_CP06 and EAP 4.3.0.GA_CP04.

    Problems Trying To Resolve

    Transaction sticky fucntionality tries to resolve 2 different issues:

    1.- Diamond Scenario

    Standalone or fat clients, such as Swing applications, that call remote methods on clustered EJBs within a transaction can suffer from the diamond scenario as explained in section 17.7 of the EJB 2.1 specification. Diamond scenario refers to a distributed transaction scenario in which data is accessed through multiple network paths. The EJB specification does not require this type of scenarios to be supported and JBoss does not currently support them. Examples:

    • SLSB deployed in a 3 node cluster with default round robin load balance policy. If you make three invocations to a method on this SLSB within a transaction, each of the invocations will land on  a different server node and a different transaction will be created in each server. If you now try to rollback the transaction, it will only be rollbacked on one of the servers.

    • Currently, there's no guarantee that a UserTransaction.begin() operation and any of the EJB invocations within that user transaction will land on the same node which means that ACID properties are no fullfiled. If EJB invocations land on the same node as UserTransaction.begin(), everything will work fine. Otherwise, each EJB invocation landing on a node where the UserTransaction is not ongoing will be treated as a separate transaction that will be commited as soon as the invocation finishes.

    2.- Transaction Failover After Invocation Reached Server

    A remote fat client starts a transaction that makes remote calls on different EJBs, can encounter issues if one of the nodes fails. Example:

    • Two clustered EJBs (EJB A and EJB B) deployed both node 1 and node 2. A remote client starts a transaction and calls EJB A, which lands on node 1. Still within the same transaction, the remote client attempts to call EJB B and the load balance policy picks node 2 but the call fails. Internally, the proxy for EJB B will attempt to failover to node 1 but won't succeed because a call within that transaction context (call to EJB A) already reached a JBoss server, even though EJB B in node 1 is working fine.

    Solution

    To get around these issues, a set transaction sticky load balance policies have been created. The aim of these is to make all EJB invocations within a transaction always land on the same node, even if different EJBs are invoked:

    • org.jboss.ha.framework.interfaces.TransactionStickyRoundRobin -> transaction sticky version of standard round robin load balance policy.

    • org.jboss.ha.framework.interfaces.TransactionStickyFirstAvailable -> transaction sticky version of standard first available load balance policy.

    • org.jboss.ha.framework.interfaces.TransactionStickyFirstAvailableIdenticalAllProxies -> transaction sticky version of standard first available identical all proxies load balance policy.

    • org.jboss.ha.framework.interfaces.TransactionStickyRandomRobin -> transaction sticky version of standard random robin load balance policy.

    Once a new transaction starts, these load balance policies fall back on the standard load balance policies to decide the transaction sticky node for the new transaction. For example, whenever a new transaction starts, TransactionStickyRoundRobin will use standard round robin to decide the transaction sticky node.

    Usage

    In order for your remote EJBs to benefit from these load balance policies, you need to do the following:

     

    Step 1.- Configure jboss:service=ClientUserTransaction MBean in conf/jboss-service.xml (or deploy/transaction-service.xml from JBoss AS 5.0.0.GA onwards) so that the proxy returned is a HA proxy configured with the desired transaction sticky load balance policy, example:

    <!--
      | UserTransaction support. 
    -->
    <mbean code="org.jboss.tm.usertx.server.ClientUserTransactionService"
      name="jboss:service=ClientUserTransaction"
      xmbean-dd="resource:xmdesc/ClientUserTransaction-xmbean.xml">
      <depends>
        <mbean code="org.jboss.invocation.jrmp.server.JRMPProxyFactory"
               name="jboss:service=proxyFactory,target=ClientUserTransactionFactory">
          <attribute name="InvokerName">jboss:service=invoker,type=unified</attribute>
          <attribute name="TargetName">jboss:service=ClientUserTransaction</attribute>
          <attribute name="JndiName">UserTransactionSessionFactory</attribute>
          <attribute name="ExportedInterface">org.jboss.tm.usertx.interfaces.UserTransactionSessionFactory</attribute>
          <attribute name="ClientInterceptors">
            <interceptors>
              <interceptor>org.jboss.proxy.ClientMethodInterceptor</interceptor>
              <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
            </interceptors>
          </attribute>
          <depends>jboss:service=invoker,type=unified</depends>
        </mbean>
      </depends>
      <depends optional-attribute-name="TxProxyName">
        <mbean code="org.jboss.proxy.generic.ProxyFactoryHA"
               name="jboss:service=proxyFactory,target=ClientUserTransaction">
          <attribute name="TargetName">jboss:service=ClientUserTransaction</attribute>
          <attribute name="JndiName"></attribute>
          <attribute name="LoadBalancePolicy">org.jboss.ha.framework.interfaces.TransactionStickyRoundRobin</attribute>
          <attribute name="ExportedInterface">org.jboss.tm.usertx.interfaces.UserTransactionSession</attribute>
          <attribute name="Partition"><inject bean="HAPartition"/></attribute>
          <attribute name="ClientInterceptors">
            <interceptors>
              <interceptor>org.jboss.proxy.ClientMethodInterceptor</interceptor>
              <interceptor>org.jboss.proxy.ClientUserTransactionStickyInterceptor</interceptor>                  
              <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
            </interceptors>
          </attribute>
          <depends optional-attribute-name="InvokerName">jboss:service=invoker,type=unifiedha</depends>
        </mbean>
      </depends>
    </mbean>

     

    Note: If using transaction sticky load balance policies with EAP 4.3.0.GA_CP04 or later, please use the following configuration instead:

     

    <!--
        | UserTransaction support.
      -->
    <mbean code="org.jboss.tm.usertx.server.ClientUserTransactionService"
      name="jboss:service=ClientUserTransaction"
      xmbean-dd="resource:xmdesc/ClientUserTransaction-xmbean.xml">
      <depends>
        <mbean code="org.jboss.invocation.jrmp.server.JRMPProxyFactory"
          name="jboss:service=proxyFactory,target=ClientUserTransactionFactory">
          <attribute name="TargetName">jboss:service=ClientUserTransaction</attribute>
          <attribute name="JndiName">UserTransactionSessionFactory</attribute>
          <attribute
    name="ExportedInterface">org.jboss.tm.usertx.interfaces.UserTransactionSessionFactory</attribute>
          <attribute name="ClientInterceptors">
            <interceptors>
              <interceptor>org.jboss.proxy.ClientMethodInterceptor</interceptor>
              <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
            </interceptors>
          </attribute>
          <depends optional-attribute-name="InvokerName">jboss:service=invoker,type=unified</depends>
        </mbean>
      </depends>
      <depends optional-attribute-name="TxProxyName">
        <mbean code="org.jboss.proxy.generic.ProxyFactoryHA"
          name="jboss:service=proxyFactory,target=ClientUserTransaction">
          <attribute name="TargetName">jboss:service=ClientUserTransaction</attribute>
          <attribute name="JndiName"></attribute>
          <attribute
    name="LoadBalancePolicy">org.jboss.ha.framework.interfaces.TransactionStickyRoundRobin</attribute>
          <attribute name="ExportedInterface">org.jboss.tm.usertx.interfaces.UserTransactionSession</attribute>
          <attribute name="ClientInterceptors">
            <interceptors>
              <interceptor>org.jboss.proxy.ClientMethodInterceptor</interceptor>
              <interceptor>org.jboss.proxy.ClientUserTransactionStickyInterceptor</interceptor>
              <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
            </interceptors>
          </attribute>
          <depends
    optional-attribute-name="PartitionObjectName">jboss:service=${jboss.partition.name:DefaultPartition}</depends>
          <depends optional-attribute-name="InvokerName">jboss:service=invoker,type=unifiedha</depends>
        </mbean>
      </depends>
    </mbean> 

     

    Step 2.- Configure your EJBs with the desired load balance policy in your EJBs' jboss.xml using the classes specified above. Examples:

     

    Important Note: Remote UserTransaction calls should be done using WhatIsTheCorrectPatternForUserTransactions but for simplicity, this wiki avoids using it.

     

    • Single EJB - transaction spans home and business calls: If the remote client calls the EJB home and business methods within the transaction, make sure you modify the bean's home-load-balance-policy and bean-load-balance-policy:

    TimeTellerHome home = (TimeTellerHome)ctx.lookup("ejb/TimeTellerEjb");
    UserTransaction tx = (UserTransaction)ctx.lookup("UserTransaction");
    tx.begin();
    TimeTeller teller = home.create();
    System.out.println(teller.whatsTheTime());
    System.out.println(teller.whatsTheTime());
    tx.commit();

    Here's the jboss.xml for the time teller EJB:

    <?xml version="1.0"?>
    <!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 4.0//EN"
            "http://www.jboss.org/j2ee/dtd/jboss_4_0.dtd">
    <jboss>
      <enterprise-beans>
        <session>
          <ejb-name>TimeTellerEjb</ejb-name>
          <jndi-name>ejb/TimeTellerEjb</jndi-name>
          <configuration-name>User Transaction Sticky Clustered Stateless SessionBean</configuration-name>
          <clustered>true</clustered>
          <cluster-config>
            <partition-name>${jboss.partition.name:DefaultPartition}</partition-name>
            <home-load-balance-policy>org.jboss.ha.framework.interfaces.TransactionStickyRoundRobin</home-load-balance-policy> 
            <bean-load-balance-policy>org.jboss.ha.framework.interfaces.TransactionStickyRoundRobin</bean-load-balance-policy>
          </cluster-config>
        </session>
      </enterprise-beans>
    
      <invoker-proxy-bindings>
        <invoker-proxy-binding>
          <name>ustxsticky-clustered-stateless-unified-invoker</name>
          <invoker-mbean>jboss:service=invoker,type=unifiedha</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.SingleRetryInterceptor</interceptor>
                <interceptor>org.jboss.proxy.TransactionStickyInterceptor</interceptor>
                <interceptor call-by-value="false">org.jboss.invocation.InvokerInterceptor</interceptor>
                <interceptor call-by-value="true">org.jboss.invocation.MarshallingInvokerInterceptor</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.SingleRetryInterceptor</interceptor>
                <interceptor>org.jboss.proxy.TransactionStickyInterceptor</interceptor>               
                <interceptor call-by-value="false">org.jboss.invocation.InvokerInterceptor</interceptor>
                <interceptor call-by-value="true">org.jboss.invocation.MarshallingInvokerInterceptor</interceptor>
              </bean>
            </client-interceptors>
          </proxy-factory-config>
        </invoker-proxy-binding>    
      </invoker-proxy-bindings>
        
      <container-configurations>
        <container-configuration extends="Clustered Stateless SessionBean">
          <container-name>User Transaction Sticky Clustered Stateless SessionBean</container-name>
          <invoker-proxy-binding-name>ustxsticky-clustered-stateless-unified-invoker</invoker-proxy-binding-name>         
        </container-configuration>
      </container-configurations>
    
    </jboss>
    • Single EJB - transaction spans only business calls: If the remote client calls only EJB business methods within the transaction, you only need to modify the bean's bean-load-balance-policy:
    TimeTellerHome home = (TimeTellerHome)ctx.lookup("ejb/TimeTellerEjb");
    TimeTeller teller = home.create();
    UserTransaction tx = (UserTransaction)ctx.lookup("UserTransaction");
    tx.begin();
    System.out.println(teller.whatsTheTime());
    System.out.println(teller.whatsTheTime());
    tx.commit();

    Here's the jboss.xml for the time teller EJB:

    <?xml version="1.0"?>
    <!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 4.0//EN"
            "http://www.jboss.org/j2ee/dtd/jboss_4_0.dtd">
    <jboss>
      <enterprise-beans>
        <session>
          <ejb-name>TimeTellerEjb</ejb-name>
          <jndi-name>ejb/TimeTellerEjb</jndi-name>
          <configuration-name>User Transaction Sticky Clustered Stateless SessionBean</configuration-name>
          <clustered>true</clustered>
          <cluster-config>
            <partition-name>${jboss.partition.name:DefaultPartition}</partition-name>
            <bean-load-balance-policy>org.jboss.ha.framework.interfaces.TransactionStickyRoundRobin</bean-load-balance-policy>
          </cluster-config>
        </session>
      </enterprise-beans>
    
      <invoker-proxy-bindings>
        <invoker-proxy-binding>
          <name>ustxsticky-clustered-stateless-unified-invoker</name>
          <invoker-mbean>jboss:service=invoker,type=unifiedha</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.SingleRetryInterceptor</interceptor>
                <interceptor call-by-value="false">org.jboss.invocation.InvokerInterceptor</interceptor>
                <interceptor call-by-value="true">org.jboss.invocation.MarshallingInvokerInterceptor</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.SingleRetryInterceptor</interceptor>
                <interceptor>org.jboss.proxy.TransactionStickyInterceptor</interceptor>               
                <interceptor call-by-value="false">org.jboss.invocation.InvokerInterceptor</interceptor>
                <interceptor call-by-value="true">org.jboss.invocation.MarshallingInvokerInterceptor</interceptor>
              </bean>
            </client-interceptors>
          </proxy-factory-config>
        </invoker-proxy-binding>    
      </invoker-proxy-bindings>
        
      <container-configurations>
        <container-configuration extends="Clustered Stateless SessionBean">
          <container-name>User Transaction Sticky Clustered Stateless SessionBean</container-name>
          <invoker-proxy-binding-name>ustxsticky-clustered-stateless-unified-invoker</invoker-proxy-binding-name>         
        </container-configuration>
      </container-configurations>
    
    </jboss>
    • Multiple EJBs - transaction spans home and business calls on multiple EJBs: Finally, if multiple EJBs are called remotely within a transaction started, make sure you modify all remote EJBs jboss.xml:
    TimeTellerHome homeTimeTeller = (TimeTellerHome)ctx.lookup("ejb/TimeTellerEjb");
    NameTellerHome homeNameTeller = (NameTellerHome)ctx.lookup("ejb/NameTellerEjb");
    UserTransaction tx = (UserTransaction)ctx.lookup("UserTransaction");
    tx.begin();
    TimeTeller timeTeller = homeTimeTeller.create();
    NameTeller nameTeller = homeNameTeller.create();
    System.out.println(timeTeller.whatsTheTime());
    System.out.println(nameTeller.whatsTheTime());
    tx.commit();

    Here's the jboss.xml for the time teller EJB:

    <?xml version="1.0"?>
    <!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 4.0//EN"
            "http://www.jboss.org/j2ee/dtd/jboss_4_0.dtd">
    <jboss>
      <enterprise-beans>
        <session>
          <ejb-name>TimeTellerEjb</ejb-name>
          <jndi-name>ejb/TimeTellerEjb</jndi-name>
          <configuration-name>User Transaction Sticky Clustered Stateless SessionBean</configuration-name>
          <clustered>true</clustered>
          <cluster-config>
            <partition-name>${jboss.partition.name:DefaultPartition}</partition-name>
            <home-load-balance-policy>org.jboss.ha.framework.interfaces.TransactionStickyRoundRobin</home-load-balance-policy> 
            <bean-load-balance-policy>org.jboss.ha.framework.interfaces.TransactionStickyRoundRobin</bean-load-balance-policy>
          </cluster-config>
        </session>
    
        <session>
          <ejb-name>NameTellerEjb</ejb-name>
          <jndi-name>ejb/NameTellerEjb</jndi-name>
          <configuration-name>User Transaction Sticky Clustered Stateless SessionBean</configuration-name>
          <clustered>true</clustered>
          <cluster-config>
            <partition-name>${jboss.partition.name:DefaultPartition}</partition-name>
            <home-load-balance-policy>org.jboss.ha.framework.interfaces.TransactionStickyRoundRobin</home-load-balance-policy> 
            <bean-load-balance-policy>org.jboss.ha.framework.interfaces.TransactionStickyRoundRobin</bean-load-balance-policy>
          </cluster-config>
        </session>
      </enterprise-beans>
    
      <invoker-proxy-bindings>
        <invoker-proxy-binding>
          <name>ustxsticky-clustered-stateless-unified-invoker</name>
          <invoker-mbean>jboss:service=invoker,type=unifiedha</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.SingleRetryInterceptor</interceptor>
                <interceptor>org.jboss.proxy.TransactionStickyInterceptor</interceptor>
                <interceptor call-by-value="false">org.jboss.invocation.InvokerInterceptor</interceptor>
                <interceptor call-by-value="true">org.jboss.invocation.MarshallingInvokerInterceptor</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.SingleRetryInterceptor</interceptor>
                <interceptor>org.jboss.proxy.TransactionStickyInterceptor</interceptor>               
                <interceptor call-by-value="false">org.jboss.invocation.InvokerInterceptor</interceptor>
                <interceptor call-by-value="true">org.jboss.invocation.MarshallingInvokerInterceptor</interceptor>
              </bean>
            </client-interceptors>
          </proxy-factory-config>
        </invoker-proxy-binding>    
      </invoker-proxy-bindings>
        
      <container-configurations>
        <container-configuration extends="Clustered Stateless SessionBean">
          <container-name>User Transaction Sticky Clustered Stateless SessionBean</container-name>
          <invoker-proxy-binding-name>ustxsticky-clustered-stateless-unified-invoker</invoker-proxy-binding-name>         
        </container-configuration>
      </container-configurations>
    </jboss>

     

    Note On Examples

    The location of the EJB home JNDI lookup is irrelevant. Doesn't matter whether it happens inside or outside the transaction.