All Examples All Cluster Examples
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 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.
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:
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:
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:
We provide separate build scripts for Windows NT and UNIX:
The "build" scripts build the example, such as this entry for Windows NT:
$ buildThese 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
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:
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.
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=DEMOYou'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.
# 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.
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.
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.
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:
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.
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.
Copyright © 1999-2000 BEA Systems, Inc. All rights reserved.
Last updated 03/17/2000