Clustering Stateless Session Beans
01/18/2001
"Holy scalability, Batman! How are we going to get the
BatWeb to support 100,000 concurrent Gothamites using our patented
BatEJB technology? Our BatWeb is primarily built with stateless
session BatEJBs on a single BatServer and now you them to work on our
BatCluster? Will this work?!?"
Well, Robin, of course it will. There are several architectures
that application server vendors can use to make your session beans
more scalable and more fault tolerant. In my previous article, EJB 2 Clustering
with Application Servers, I talked about the generic approaches
that a vendor can use to load balance and cluster EJBs, focusing on
the importance of load balancing and fail over logic contained
directly within the home and remote stubs downloaded to a remote
client. This article will expand upon that approach for stateless
session EJBs by demonstrating additional load balancing and fail over
techniques.
Load Balancing
The first enhancement that application server vendors can implement
is load balancing of client invocations to EJBs in different servers
or different virtual machines. (See Figure 1: Big Picture of Stateless
Session Beans.) Since all instances of a stateless session bean type
are identical, a home or remote stub can have its request serviced by
any available object on any server. The remote stub doesn't care
whether an instance in the first or the second server handles the
request, as long as the request gets handled.
 |
Figure 1: Big
Picture of Stateless Session Beans. |
Application servers can scale if they're configured such that home
stubs and remote stubs balance the load of method invocations across
servers. Each method call handled by a stub would execute a
ServerChooser algorithm in the stub to select which
server is best suited for handling this method invocation. The
ServerChooser would return the IP address or connection
to the server that best meets the criteria needed for this method
invocation. Then the stub forwards the invocation to that server.
Since all stateless session EJBs are identical, the home and remote
stubs can select, with some flexibility, the server to handle
particular requests. Popular types of balancing algorithms in use
today include the five following.
Random -- A seed-based, random selection of a server to
handle a request; may incorporate statistical irregularities over the
short term.
Round Robin -- Sends requests to different servers in a
statistically regular way.
Weighted Round Robin -- A derivative of round robin, it
favors certain servers using ratios. Administrators and developers
apply relative weights to different servers that determine how much of
the load they should handle. One design pattern is to set the weight
of a server that needs some maintenance to zero, thus eliminating any
EJB traffic to that server.
High Availability - A derivative of the round-robin
algorithm, it "pins" a stub to a single server until the server can no
longer handle the request, at which time the stub makes a "high
availability" switch to the next server (which is typically done in a
round-robin fashion).
Programmatic - In some application servers you can write
your own stub-based balancing algorithm as a Java class compiled into
the stub implementation. The class that you implement typically has a
single method that is executed whenever a remote or home stub has to
select a server. The method that you implement will typically have
access to reflection method and input arguments that relate to the
method that the client invoked. From this information, the method
generates a list of servers or IP addresses of servers to try when the
stub needs to load balance. The order of this list dictates the order
of the balancing scheme applied by the stub. This is a great way to
implement a staging, test, and production environment for your EJBs.
Depending upon the values of the input parameters or the current value
of a flag specified in a text file, your programmatic filter can alter
requests to different servers depending upon whether you are testing
an application or running it in production mode.
Fault Tolerance
Load balancing sounds easy to incorporate, doesn't it? Well, for
the most part, it is. However, incorporating fault tolerance and fail
over into your EJB applications is more complicated. There are many
issues involved in accomplishing a successful fail over (and to
supporing fault tolerance).
How does a home or remote stub determine whether a failure
situation has occurred? If an exception is generated, how does a stub
know that the exception is a failure condition that cannot be
recovered? There are three types of exceptions that a stub can
receive: application exceptions (those defined by the bean developer),
system exceptions (typically in the form of
EJBException), and network/communication exceptions.
Application and system exceptions are clearly defined in the EJB
specification and are typically propagated to the invoking client to
be handled. Network/communication exceptions, such as
SocketException, CommunicationException, and
others, indicate a much more dire, unable-to-communicate-with-server
scenario. An application server stub can intercept all system and
communication exceptions and may perform a fail over to another
server, if it's safe to do so. Why wouldn't it be safe?
At what point in the invocation did the failure occur? There
are three situations to consider.
- The failure occurred before the server-side
invocation started. If the stub can determine that a failure
occurred after the method request was sent but before it was invoked
on the server, the stub can treat this failure as a load balance
scenario and re-direct the method invocation to any other available
server.
- The failure occurred after the server-side invocation
completed, but before the response message was properly propagated to
the client. This situation doesn't require any further action from
the stub.
- The failure occurred during the server-side invocation. The
method that was invoked on the server may or may not have altered some
server-side resources that impact future behavior of the system. If
the stub makes a subsequent request on a method that impacts
server-side resources or data, the stub may inadvertently perform the
same altering action twice in a row during a failure recovery, thus
causing the system to behave indeterminately. If the method doesn't
perform any of these altering actions, then it could safely invoke the
method again. But if it does alter the system, it cannot.
Unfortunately, even though the (a) and (b) scenarios have a very
happy ending, it is difficult to determine for certain which state the
system is in. As a result, a stub will likely have to assume that if
a failure situation occurs, the server was in (c), the worst-case
scenario, even if in fact it was in (a) or (b).
So should the application server do? Many application servers
support idempotent methods, which yield the same results on subsequent
invocations if the same input arguments are provided. A
non-idempotent method alters the state of the system (that is, yields
different results) on subsequent executions. Types of methods that
are idempotent include
- any method that acts as a "reader / getter" method,
- any method that performs a non-altering query, and
- any method that accesses a resource manager, but leaves the state
of the resource manager unaltered.
As a bean developer you are aware of the behavior of the methods
you. Using the utilities provided by the vendor, you specify the
idempotent and non-idempotent bean methods. At runtime, an
application server that encounters a failure situation during a method
invocation can freely perform a fail over switch if the method that
was being executed is idempotent. After all, if the method doesn't
alter the state of the system, then the stub may assume the existing
system is intact and that a new invocation will not have odd side
effects. In situations where a failure occurs on a method that is
not idempotent, the stub will be forced to throw the communication
exception it receives to the client. Your client application will
have to determine whether it should try another invocation or not.
try {
MyHome home = (MyHome) ctx.lookup("MyEJB");
// This should use PortableRemoteObject.narrow().
My remote = (My) home.create();
remote.invokeIdempotentMethod();
} catch (javax.naming.CommunicationException exception) {
// This is a type of Naming Exception
// This is an acceptable exception for me. Calling this method again
// will not adversely impact the system.
try {
remote.invokeIdempotentMethod();
} catch (java.io.IOException exception) {
// This must be some sort of socket exception.
// I don't want to call this method again, so now it must be
// handled in another way that you determine!
}
}
Conclusion
As you can see, a variety of options exist for smoothly integrate
stateless session EJBs in clustered implementations. Obviously I have
not addressed stateful session beans. Statefulness adds another level
of complexity since stateful bean stubs are "pinned" to the object
that they represent. This reduces load balance and fail over
flexibility, but they may still be done. How they are done is the
topic of the next article in this clustering series. Until then, good
luck and happy scaling!
Tyler Jewell
, Director, Technical Evangelism, BEA Systems Tyler oversees BEA's technology evangelism efforts that are focused on driving early adoption of strategic BEA technologies into the ISV and developer community.
Read more EJB 2 columns.
Return to ONJava.com.