All Examples  All Cluster Examples

examples.cluster.ejb

Cluster Enterprise JavaBean
example packages and classes

about this example

Note: You'll need a cluster license in order to run this example in a cluster environment. Contact your sales rep for more information.

This example demonstrates how to write a clustered application with Enterprise JavaBeans that exhibits load balancing and failover.

Before running this example, you should run the other EJB examples as they will shows the steps that are involved for all the different types of EJBeans. This is a complicated and sophisticated example that uses advanced techniques. It's assumed that you are familar with EJBeans, have run the other examples and have written EJBeans yourself.

The example consists of a Client application that uses a stateless session EJBean TellerBean. The TellerBean conducts transactions on behalf of the Client -- such as looking up an account balance, performing deposits, withdrawls and transfers -- using an entity EJBean AccountBean.

The example demonstrates:

The Client application performs these steps:
  1. Contacts the Teller home ("cluster.ejb.TellerHome") through JNDI to find the EJBean's home
  2. Creates a Teller
  3. A loop is executed five times:
    1. Gets the balance of account "10000"
    2. Deposits an amount into account "10000"
    3. Withdraws a (smaller) amount from account "10000"
    4. Transfers an amount from account "10000" to account "10005"
    5. With each transaction, it reports balances and the server where the transaction occurred (where the Teller and Account beans are)
  4. Calculates the number of invocations on each server in the cluster, using the utility class examples.cluster.utils.ClusterUtils.
The application demonstrates how the Client must decide in the event of a failure if a transaction should be retried or not. The Client determines this by asking a Teller to consult the transaction log (maintained in a separate table) and see if the transaction ID is there. If it is, the transaction was committed, irrespective of any exceptions. If not, the transaction can be attempted again if the exception received was a RemoteException, indicating a system failure.

The statistics will show how all the invocations are distributed among the different servers that compose the cluster. Notice that the entity beans are colocated on the same server as the teller when called from within a transaction, and may appear on a different server when called outside of a transaction, such as looking up an account balance.

Designing for failover

The example demonstrates how you design an application to failover and retry transactions when specific exceptions and conditions occur. This code snippet from the Client shows how the Client determines if the transaction was completed and if it should be retried:

    void transaction() throws Exception, TellerException, RemoteException, CreateException {
      trans++;
      int attempts      = 0;
      int set           = i;
      boolean committed = false;
      boolean invoke    = true;
      System.out.println("Transaction " + trans + " of set " + (set + 1) +
                         " (" + transId + ")");
      while (attempts < MAXATTEMPTS) {
        try {
          attempts++;
          System.out.println("  Attempt " + attempts);
          if (teller == null)
            teller = bank.create();
          if (invoke) {
            invokeTransaction();
            buildReport();
            printReport();
            System.out.println("  End of transaction " + trans + 
                               " of set " + (set + 1));
            return;
          } else { // checking transaction
            committed = teller.checkTransactionId(transId);
            System.out.println("    Checked transaction " + transId + 
                               ": committed = " + committed);
            if (committed) {
              return;
            } else {
              System.out.println("    Attempting Transaction " + trans + " again");
              invoke = true;
            }
          }
        }
        catch (RemoteException re) {
          System.out.println("    Error: " + re);
          // Replace teller, in case that's the problem
          teller = null;
          invoke = false;
        }
      }
      throw new Exception("  Transaction " + trans + " of set " +
                          (set + 1) + " ended unsuccessfully");
    }

Let's look at how the loop and exception handling occurs.

A "while" loop is started, and increments the number of attempts. It then creates a bean from the home (bank) if teller==null.

If a Teller is sucessfully created, a transaction is tried. If that suceeds, the Teller will continue to be used for further transactions.

If the transaction fails because of a RemoteException, the bank is asked for a new Teller (an idempotent method that can failover to another server), and the transaction is checked to see if it actually was committed or not..

If the check returns that the transaction was not committed, an attempt is made to try the transaction again. Otherwise, the loop is exited. (You'll notice that we put the check inside the same loop as the call to invokeTransaction(), and in the client output, the call to the check will be described as an "Attempt". This is done to allow the check to failover to another server if necessary.)

If a method on the Teller fails because of any other exception (such as a TellerException), it is assumed to be fatal and the loop is exited.

The test attempts < MAXATTEMPT sets an upper bound on the loop. If it is reached, the Client will stop.

In summary, when writing a clustered application with failover:

Designing logging

An essential part of a clustered application that exhibts failover is being able to confirm that a transaction was successfully completed in the event of an exception. In this example, we do that by logging all the transactions in a database table, and including that logging as part of the transaction.

The log table must reside on the same database server as the other table(s) that are used for EJBean persistence, as the JTA driver used supports only one connection pool at a time. This connection pool is specified in the deployment descriptor for the TellerBean as "logPoolName oraclePool" and must be the same as the connection pool in the AccountBean.

Methods in the TellerBean (setTransactionId() and checkTransactionId(d)) are used to write and check the log table. For efficencey, they directly access the database using database-specific SQL code.

setTransactionId() writes to the log table in an LRU-fashion: it inserts records into the table until the table reaches a maximum size, and from then on updates the oldest existing record with the newest information. In practice, you'd need to size this table based on the number of transactions that you'd expect to log in the period of time between when a failure occured and when you'd want to check a transaction's status. Typically this would be a very short period of time, so the table would not have to be very large to handle a large number of transactions.

There are a number of assumptions here:

how to use this example

To get the most out of this example, first read through the source code files to see what is happening. Start with the deployment descriptors for the Teller and Account EJBeans to find the general structure of the EJBeans, which classes are used for the different objects and interfaces, then look at the Client code to see how the application works.

In general, you'll need to adjust certain properties in the deployment descriptors to match your setup before you build the EJBeans. You'll need to edit the entry for the property that begins with "weblogic.ejb.deploy" in the weblogic.properties file to deploy the EJBeans. The property is commented out in the default properties file; make sure that you uncomment out all the lines of the property. You'll also need to configure an Oracle database for use as the persistent store.

These three sections cover what to do:

  1. Build the example
  2. Set your environment
  3. Run the example

Build the example

Set up your development environment as described in Setting your development environment.

We provide separate build scripts for Windows NT and UNIX:

The "build" scripts build the example, such as this entry for Windows NT:

$ build
These scripts will build the example and place the files in the correct locations. Additional information on using the build scripts is found in Building Enterprise JavaBean examples

Set your environment

  1. Set the JDBC persistence. The Persistence in the deployment descriptor is already set to "jdbc" for database persistence.

    With database persistence, each instance of an EJBean is written to a row in a table. The tables used by this example (ejbAccounts, used for the entity AccountBean and ejbTransLog, used for the transaction log table) must be created and exist in the database before the example is run. The SQL below will create the tables and populate the ejbAccounts table.

    You'll need to:

    1. Create the tables in your database using appropriate SQL statements such as this example for Oracle (found in the file oracle.ddl):
        DROP TABLE ejbAccounts;
        CREATE TABLE ejbAccounts (id varchar(15), bal float, type varchar(15));
        INSERT INTO ejbAccounts (id,bal,type) VALUES ('10000',1000,'Checking');
        INSERT INTO ejbAccounts (id,bal,type) VALUES ('10005',1000,'Savings');
        DROP TABLE ejbTransLog;
        CREATE TABLE ejbTransLog (transId varchar(32), transCommitDate date);
      If you use a database that's different than Oracle, you may need to change both the SQL code given above and the SQL code in the TellerBean methods setTransactionId() and checkTransactionId().

      You can use the Schema utility to upload these statements to your database.

    2. You'll need to setup a connection pool called "oraclePool" in the weblogic.properties file:
          weblogic.jdbc.connectionPool.oraclePool=\
               url=jdbc:weblogic:oracle,\
               driver=weblogic.jdbc.oci.Driver,\
               loginDelaySecs=1,\
               initialCapacity=2,\
               maxCapacity=10,\
               capacityIncrement=2,\
               props=user=SCOTT;password=TIGER;server=DEMO
      You'll need to adjust the server name to the name of your Oracle instance. If you need to use a different connection pool, you'll need to change the name in the deployment descriptor for the AccountBean.

    3. You'll need to add an access control list (ACL) for users of the pool:
        # Add an ACL for the connection pool:
        weblogic.allow.reserve.weblogic.jdbc.connectionPool.oraclePool=guest

      If you need more information about how to use connection pools, read Using WebLogic JDBC: Using connection pools.

  2. Deploy the EJBeans by adding the jar file cluster_ejb.jar found in the /weblogic/myserver directory to the "weblogic.ejb.deploy" property in the weblogic.properties file. We provide a commented-out version that begins with "weblogic.ejb.deploy" that you can use. You'll need to adjust the property depending on which EJBeans you're building and are deploying.

    Note: If you're running under the Microsoft SDK for Java, you'll also need to add the path to the .jar to the CLASSPATH for your WebLogic Server.

    All EJBeans must be deployed from the "per-cluster" properties file in order for them to take advantage of load balancing and failover.

Run the example

  1. Start the WebLogic Cluster

    For instructions on starting a WebLogic cluster and developing applications that use WebLogic Clusters, read WebLogic Clusters. .

    You can check that the EJBeans have been deployed correctly either by checking the server command line window, or by opening the Console and examining "EJB" under the "Distributed Objects"; you should see TellerBean and AccountBean deployed, and can monitor their activity.

  2. Start the Client

    Run the client in a separate command line window. Set up your client as described in Setting your development environment.

    After at least thirty seconds have passed (to allow the cluster to stabilize), run the client by entering in a separate command line window:

    $ java examples.cluster.ejb.Client "t3://WebLogicClusterName:Port"

    where:

    WebLogicClusterName
    DNS cluster name for the WebLogic Cluster
    Port
    Port that is listening for connections (weblogic.system.ListenPort)

  3. Demonstrating load balancing

    If you're running the Client example, you should get output similar to this from the client application:
    Beginning cluster.ejb.Client...
    
    Start of transaction set 1
    Transaction 1 of set 1 (921736154358)
      Attempt 1
        Teller (server1): balance of account
        Account 10000 (on server1): balance: $1000.0
      End of transaction 1 of set 1
    Transaction 2 of set 1 (921736160898)
      Attempt 1
        Teller (server3): deposited $100.0
        Account 10000 (on server3): balance: $1100.0
      End of transaction 2 of set 1
    Transaction 3 of set 1 (921736171934)
      Attempt 1
        Teller (server2): withdrew $50.0
        Account 10000 (on server2): balance: $1050.0
      End of transaction 3 of set 1
    Transaction 4 of set 1 (921736242936)
      Attempt 1
        Teller (server1): Transfered $50.0 from 10000 to 10005
        Account 10000 (on server1): balance: $1000.0
        Account 10005 (on server1): balance: $1050.0
      End of transaction 4 of set 1
    End of transaction set 1
    
    Start of transaction set 2
    Transaction 1 of set 2 (921736257437)
     .
     .
     .
    
    Statistics for different servers:
    
    "server3" processed 16 (36%) of 45 invocations
    "server2" processed 14 (31%) of 45 invocations
    "server1" processed 15 (33%) of 45 invocations
    
    End cluster.ejb.Client...
    Notice how the invocation load is balanced across the different servers in the Cluster. Because the entity beans are colocated with the session beans, the load is not balanced identically on all servers, as one of the transactions calls two entity beans.

  4. Demonstrating failover

    You can also see failover from one server to the next. After the client has started running, bring down one of the servers at an appropriate point while running the client application.

    You can bring down a server by either typing "Control-C" in the command line window for the server (Windows) or by issuing a "kill" command using the process ID of the server to be brought down (UNIX).

    If you watch in the server output logs, you'll see lines such as these, followed by the server pausing:

    teller.transaction: tx.commit() about to be called: failover test point
    
    account.ejbStore (1724741, PK = 10000): failover test point

    These indicate that the server is about to invoke on an EJBean, and are good locations to test failover.

    You should get output similar to this from the client application:

    D:\weblogic\src40_117\examples>Beginning cluster.ejb.Client...
    
    Start of transaction set 1
    Transaction 1 of set 1 (921792857443)
      Attempt 1
        Teller (server1): balance of account
        Account 10000 (on server1): balance: $1400.0
      End of transaction 1 of set 1
    Transaction 2 of set 1 (921792862891)
      Attempt 1
        Error: java.rmi.RemoteException: ; nested exception is:
            weblogic.rjvm.PeerGoneException: JVM 7645352540945089822S172.17.12.34:[7
    001,7001,7002,7002] is gone
      Attempt 2
        Checked transaction 921792862891: committed = false
        Attempting Transaction 2 again
      Attempt 3
        Teller (server1): deposited $100.0
        Account 10000 (on server1): balance: $1500.0
      End of transaction 2 of set 1
    Transaction 3 of set 1 (921793006107)
      Attempt 1
        Teller (server3): withdrew $50.0
        Account 10000 (on server3): balance: $1450.0
      End of transaction 3 of set 1
    Transaction 4 of set 1 (921793016331)
      Attempt 1
        Teller (server1): Transfered $50.0 from 10000 to 10005
        Account 10000 (on server1): balance: $1400.0
        Account 10005 (on server1): balance: $2100.0
      End of transaction 4 of set 1
    End of transaction set 1
    
    Start of transaction set 2
     .
     .
     .
    Note how after one of the servers was killed as it was attempting to commit the transfer, the transaction was then checked, found to have not been committed, and was attempted again successfully. If the server was killed after the committ had successfully been sent to the database, the check of the transaction would have shown that the commit was succesful, and the transaction would not be attempted again.

there's more

Read more about:

Copyright © 1999-2000 BEA Systems, Inc. All rights reserved.

Last updated 03/17/2000