ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Seamlessly Caching Stubs for Improved Performance

by William Grosso, author of Java RMI
10/31/2001

This is the second of a three-part series on the Remote Method Invocation framework, or RMI -- a powerful framework for building distributed applications. RMI, which comes with Java 2, Standard Edition, lets two different applications communicate using method calls that look and feel remarkably like ordinary in-process method calls. Two problems with RMI, however, are that it's easy to repeat the same code in lots of different places, and to write an inefficient application that makes far too many remote calls. In this series, we discuss one way to organize the RMI-specific code of a client application to solve these two problems, by introducing command objects to encapsulate remote method calls. If you're not familiar with RMI, take a look at the author's book, Java RMI. To learn about command objects, consult Design Patterns: Elements of Reusable Object-Oriented Software.

Abstract

In the first article in this series, Command Objects and RMI, I introduced a distributed translation service and showed how the use of a simple command object framework to encapsulate retry logic simultaneously simplified the client application and made it more robust.

In this article, I discuss a common structural problem for client applications: they wind up either making an excessive number of remote method calls to a naming service or implementing some form of local cache for the naming service. After outlining the problem, I'll show you how to extend the command object framework, introduced in the first article, to provide seamless caching of stubs. Using these new extensions will make your RMI code simpler and more robust.

Once again, I'd like to state that these articles require a fair amount of RMI knowledge and experience. If you're not familiar with RMI, my book Java RMI is a pretty good place to start.

The source code for this article can be downloaded by clicking here. This code has been tested using the beta of JDK 1.4 on Windows NT systems, but it should compile and run against JDK 1.3.

Distributed Applications Often Waste Bandwidth

Let's start by recalling the client application -- it allowed the user to ask a remote service to translate a word. The GUI part of the client application was very simple as well. It provided ways for the user to enter a word, (manually) choose a translation service, and actually get a word translated. Here's what the GUI looked like:

Screen shot.

To use the program, the user fills out all the text fields and then presses the Translate Word Now button. The program then executes the following lines of code (which are contained in a listener attached to the Translate Word Now button):

String resultText = "";
Translator translator = _translatorPanel.getTranslator();
Word word = _wordPanel.getWord();
Language targetLanguage = _translatorPanel.getTargetLanguage();
TranslateWord translateMethod = new TranslateWord(translator, word, targetLanguage);
try {
  Word result = (Word) translateMethod.makeCall();
  resultText = result.toString();
}
catch (CouldNotTranslateException CNTE) {
  resultText = COULD_NOT_TRANSLATE_STRING;
}
catch (Exception e) {
  resultText = e.toString();
}
finally {
  _resultsPanel.setText(resultText);
}

This code fetches a stub from the appropriate RMI registry (that's what the call to getTranslator does), builds a command object around the stub, and then executes the remote call.

What happens if the user enters a second word and then clicks the Translate Word Now button? In that case, the code fetches a stub from the appropriate RMI registry, builds a command object around the stub, and then executes the remote call.

The same lines of code are executed evey time the button is pressed. This means that each time the user clicks the button, two remote method calls are being made: one to fetch the stub for the translator from the remote registry and one to actually make the translate call to the server performing the translation. This happens even if the same server is used to perform the translations.

Also in this series:

Learning Command Objects and RMI -- O'Reilly's Java RMI author William Grosso introduces you to the basic ideas behind command objects by providing a translation service from a remote server and using command objects to structure the RMI made from a client program.

Generics and Method Objects -- O'Reilly's Java RMI author William Grosso introduces you to the new Generics Specification and rebuilds his command object framework using it.

This is horribly inefficient. Remote method calls are slow: most tests indicate that a simple remote method call is at least 1,000 times slower than an ordinary, in-process method call (and this will only get worse -- processor speed is increasing at a faster rate than network speed). And the situation is even worse than it appears because it's not just one application that is being affected. Every remote method call decreases the amount of bandwidth available on the network for all applications. Doubling the number of remote calls made by a client-server application is, hands down, one of the worst design decisions imaginable.

It's also a decision that's made every day.

Fortunately, there's an obvious solution. Instead of fetching the stub each time, simply fetch the stub the first time and store it in a local stub cache (e.g., in a hash table in memory). The second time the translator is used, the stub is already available locally and, hence, only one remote method invocation is necessary.

In this article, I'll show you how to build a local stub cache in a principled way. There are two main goals:

  • The local cache of stubs should be integrated with our command objects. A programmer who writes a remote method call should not have to worry about the details of cache management.
  • The cache should only contain valid stubs.

Implementing a Local Cache to Hold Stubs

In this article, we'll first build a stub cache, and then integrate the stub cache into the command object framework. The first implementation of a stub cache is fairly simple: it takes instances of ServerDescription and returns instances of RemoteStub. That is, the public interface for RemoteStubCache contains the following two static methods:

  public static RemoteStub getStubToRemoteObject(ServerDescription serverDescription) throws ServerUnavailable
  public static void removeStubFromCache(ServerDescription serverDescription)

Objects that need to get a stub call getStubToRemoteObject; objects that have bad stubs (for example, stubs to a server which has crashed) inform the cache by calling removeStubFromCache. Both of these methods take a single argument, an instance of ServerDescription.

ServerDescription is a simple object: it contains enough information to find the registry a server is bound into, along with the name of the server. RemoteStubCache consists of some static methods to access the cached stubs, based on an instance of ServerDescription. Here's the code for ServerDescription:

public class ServerDescription
{
  private String _serverMachine;
  private String _serverName;
  private int _registryPort;
  private String _hashString;

  public ServerDescription(String serverMachine, String serverName, int registryPort) {
    _serverMachine = serverMachine;
    _serverName = serverName;
    _registryPort = registryPort ;
    _hashString = _serverMachine + _serverName + String.valueOf(_registryPort);

  }

  protected RemoteStub getStub() {
    RemoteStub returnValue = null;
    try {
Registry registry = LocateRegistry.getRegistry(_serverMachine , _registryPort);
return (RemoteStub) registry.lookup(_serverName);
    }
    catch (Exception ignored) {}
    return returnValue;
  }

  public int hashCode() {
    return _hashString.hashCode();
  }

  public boolean equals(Object object) {
    if (! (object instanceof ServerDescription)) {
return false;  
    }
    ServerDescription otherServerDescription = (ServerDescription) object;
    return _hashString.equals(otherServerDescription.getHashstring());
  }

  private String getHashstring() {
    return _hashString;
  }  
}

Note that we were very careful to implement equals and hashcode in a way that takes advantage of all of the data contained in an instance of ServerDescription. Doing so makes the implementation of RemoteStubCache much easier.

Related Reading

Java RMIJava RMI
By William Grosso
Table of Contents
Index
Sample Chapter
Full Description

ServerDescription (rather than RemoteStubCache) is also responsible for fetching the stubs. This might seem a little idiosyncratic, but it gives us a lot of flexibility to extend the framework. In particular, putting the stub retrieval logic into ServerDescription leaves open the possibility of making ServerDescription into an abstract base class and having more than one concrete subclass. For example, you might want different implementations of ServerDescription if the stubs are stored used different types of naming services. Extending our framework to include stubs stored in an LDAP server involves creating a new subclass of ServerDescription but doesn't actually alter existing code.

Pages: 1, 2, 3

Next Pagearrow