|
WebLogic Server 6.0 Code Examples, BEA Systems, Inc. |
See:
Description
Class Summary | |
Client | This class illustrates load balancing and failover using a container-managed JDBC EntityBean. |
This example demonstrates how to write a clustered application with Enterprise JavaBeans that exhibits load balancing and failover.
Note: You'll need a cluster license in order to run this example in a cluster environment. Contact your sales rep for more information.
In addition to the Client class, this example also uses:
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 EJBs. This is a complicated and sophisticated example that uses advanced techniques. It's assumed that you are familar with EJBs, have run the other examples and have written EJBs yourself.
The example consists of a Client application that uses a stateless session EJB 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 EJB 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 EJB 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 efficiency, 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 EJBs to find the general structure of the EJBs, which classes are used for the different objects and interfaces, then look at the Client code to see how the application works. You'll also need to configure an Oracle database for use as the persistent store.
These three sections cover what to do:
$ cd %WL_HOME%\samples\examples\cluster\ejb\account
$ build
The build script builds the example Account EJB and places the resulting .jar files in the deployment directory of the examples server. See Building Enterprise JavaBean examples for more information on using the EJB build scripts. The Account build script also compiles the sample client application and places the compiled classes in the %WL_HOME%\config\examples\clientclasses directory.
Note: BEA provides separate build scripts for Windows NT and UNIX: build.cmd and build.sh, respectively.
$ cd %WL_HOME%\samples\examples\cluster\ejb\teller
$ build
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);
Create the tables in your database using the above SQL statements, or by using the oracle.ddl file with the Schema utility.
Note: If you use a non-Oracle database, you may need to change both the SQL code given above and the SQL code in the TellerBean methods setTransactionId() and checkTransactionId().
$ java examples.cluster.ejb.Client "t3://WebLogicClusterName:Port"
where:
The client application displays output similar to the following:
Beginning examples.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 examples.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.
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 EJB, and are good locations to test failover.
You should get output similar to this from the client application:
D:\weblogic\src40_117\examples>Beginning examples.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 successful, and the transaction would not be attempted again.
|
Documentation is available at http://e-docs.bea.com/wls/docs60 |