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

advertisement

AddThis Social Bookmark Button

Seamlessly Caching Stubs for Improved Performance
Pages: 1, 2, 3

Integrating the Local Stub Cache Into Our Application

Now that we've built both RemoteStubCache and ServerDescriptionBasedRemoteMethodCall, we need to integrate them into the rest of our application. And here, we're in for a pleasant surprise -- RemoteStubCache and ServerDescriptionBasedRemoteMethodCall aren't just a nice addition to our framework that cuts the number of remote method calls in half; they also actually simplify the rest of our code. Here, for example, are the old and new versions of TranslateWord.



The version from the first article:

public class TranslateWord extends AbstractRemoteMethodCall {

  private Translator _translator;
  private Word _sourceWord;
  private Language _targetLanguage;

  public TranslateWord(Translator translator, Word sourceWord, Language targetLanguage) {
    _translator = translator;
    _sourceWord = sourceWord;
    _targetLanguage = targetLanguage;
  }

  protected Remote getRemoteObject() throws ServerUnavailable {
    return (Remote) _translator;
  }

  protected Object performRemoteCall(Remote remoteObject) throws RemoteException, Exception {
    Translator translator = (Translator) remoteObject;
    return translator.translate(_sourceWord, _targetLanguage);
  }
}

The new version:

public class TranslateWord extends ServerDescriptionBasedRemoteMethodCall  {
  private Word _sourceWord;
  private Language _targetLanguage;
  public TranslateWord(ServerDescription serverDescription, Word sourceWord, Language targetLanguage) {
    super(serverDescription);
    _sourceWord = sourceWord;
    _targetLanguage = targetLanguage;
  }

  protected Object performRemoteCall(Remote remoteObject) throws RemoteException, Exception {
    Translator translator = (Translator) remoteObject;
    return translator.translate(_sourceWord, _targetLanguage);
  }
}

The second object is actually a little shorter and cleaner. It's not a huge difference -- these two classes are recognizably the same object -- but it's still a pleasant surprise.

We extended the framework by adding three new classes and now, without any additional effort, all the classes that hook into the framework will simultaneously get some extra (and fairly sophisticated) functionality and become simpler. Now that's a good framework!

Complications That Arise from Using a Local Stub Cache

There are two basic types of servers in the world: servers that are intended to run for very long periods of time without any downtime, and servers that are intended to have a more transient lifetime. I call the second class of servers temporary servers.

Caching stubs to temporary servers is often a bad idea. The reason is that RMI implements a very nice distributed garbage collector. The way it works is simple: any stub which can be accessed by live code maintains a lease on the server. The lease is simply a promise by the RMI runtime that for a certain amount of time (the duration of the lease), the server will not be subject to garbage collecteion -- unless the server application takes extraordinary measures. When the lease is about to expire, the stub renews it.

The net effect is that if there are active stubs to a server, the server will not be picked up in garbage collection. The problem with our stub caching system is this: if an application ever obtained a stub to the server, it will cache the stub inside RemoteStubCache forever. Which means that using RemoteStubCache can prevent temporary servers from being collected as garbage.

If you're not familiar with the distributed garbage collector, and would like to learn more, it's fully covered in my book Java RMI.

You might think this isn't actually a problem for our implementation of ServerDescription. Our implementation of ServerDescription assumes that the server is bound into a running instance of the RMI registry. Stubs in the RMI registry maintain leases on the servers they reference. That means that the local stub cache is adding extra leases (one for each cached stub), but is not actually pinning the server in memory.

The problem comes when the server is removed from an RMI registry. For example, you might imagine that, at the end of the day, we use the following command object to shut down our server:

import java.rmi.*;
import java.rmi.server.*;

public class ShutdownServer extends ServerDescriptionBasedRemoteMethodCall {
  private String _reason;
  public ShutdownServer(ServerDescription serverDescription, String reason) {
    super(serverDescription);
    _reason = reason;
  }

  protected Object performRemoteCall(Remote remoteObject) throws RemoteException, Exception {
    ServerWhichCanBeShutDown server = (ServerWhichCanBeShutDown) remoteObject;
    server.shutdown(_reason);
    return null;
  }
}

You might think that, as long as the server removed itself from the RMI registry (which the client cannot do; for security reasons, code running on one computer cannot alter the contents of an instance of the RMI registry running on another computer), this would be sufficient.

But you would be wrong. The problem is that local stub caches have stubs that point to the server. Even though the server has been told to shut down, and even though its stub has been removed from the RMI registry, the local stub caches have already-fetched stubs. This is true even for the client that called shutdown -- after performRemoteCall is finished executing, the local stub cache still has a stub to the server.

A Slightly More Complicated Local Stub Cache

Of course, this problem also has an obvious solution: client programs need to occasionally remove unused stubs from RemoteStubCache. The only real difficulty is figuring out what strategy should be used to expire stubs which aren't being used. One simple, yet often effective, strategy is to implement the following two precepts:

  • Stubs that are currently referenced are active. That is, if some object other than RemotestubCache has a reference to a stub, the stub is being used for some purpose. Active stubs should not be expired. Otherwise, the stub is considered to be inactive and is a candidate for expiration.
  • If a stub has been consistently inactive for more than a fixed time, get rid of it.

Implementing this is actually a little harder than it looks. The difficult part is contained in the first bullet point: how do you tell if a stub is inactive? The answer (as you probably guessed) is that command objects make this easy -- we simply need to change the code in ServerDescriptionBasedRemoteMethodCall so that it tells the instance of RemoteStubCache when the stub is no longer being used.

In order to do this, we're simply going to add a method, noLongerUsingStubToRemoteObject, to RemoteStubCache and then override makeCall in ServerDescriptionBasedRemoteMethodCall as follows:

  public Object makeCall() throws ServerUnavailable, Exception {
    Object returnValue = null;
    try {
returnValue = super.makeCall();
    }
    finally {
RemoteStubCache.noLongerUsingStubToRemoteObject(_serverDescription);
    }
    return returnValue;
  }
  

All this does is invoke the superclass method (containing the retry logic) and then, no matter how the superclass method turns out (whether it throws an exception or not), tell RemoteStubCache that the stub is no longer being used.

After we've done this, we simply need to change RemoteStubCache to track how long a stub has been inactive, and then expire the old stubs. We're not going to do that in this series (it's out of the scope of these articles), but it's not a difficult thing to do.

The beauty of this new functionality is that the stub expiration logic is completely hidden from your code -- it was entirely implemented inside of the command object framework.

Summary and Roadmap

In this article, I extended the command object framework to seamlessly implement a local stub cache behind the scenes. In doing so, I also removed the lookup code from various places in the client, instead putting most of it into a new abstract command object, ServerDescriptionBasedRemoteMethodCall. If we revisit the previous article's cost-benefit analysis now, we'd see the following:

Pro

  • Encapsulation is good. Encapsulating method calls in separate objects makes the main program logic easier to read. ClientFrame is easy to read code and all of the details of the remote call have been placed in a separate location.
  • ServerDescription is logically clean. We've now put the naming and lookup code in a single place, and replaced calls to the RMI registry with ServerDescription objects. Remote method invocations now consist of creating a server description and passing it to a command object.
  • Latency has been reduced. Stubs are fetched once and then cached locally.
  • Difficult logic is implemented once, in the framework. The retry logic is implemented once, in an abstract base class, and is correct.
  • The client code is simple. The code required to create a new subclass of ServerDescriptionBasedRemoteMethodCall is almost trivial -- it's both easy to write and easy to see (at a glance) that it's correct.
  • The framework provides hooks. We have some very nice hooks for using different retry strategies based on context. We also have some very nice hooks for inserting logging functionality.

Con

Related Reading

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

On the other hand, we haven't really addressed some of the problems mentioned in the the first article in this series. In particular, we still have the following two problems:

  • Indirection is confusing. Extra classes, which encapsulate requests, and the attendant level of indirection can be confusing. For small applications, using command objects feels like overkill.
  • We lost some type safety. The signature of makeCall -- returning instances of Object and throwing Exception -- is a violation of good programming practices. We've lost a fair amount of type safety here.

From looking at these lists, it should be clear that command objects make life better. They simplify client code, make the client more robust, make it possible to seamlessly implement things like local stub caches, and allow you to say things like "I optimized performance of our distributed application by implementing a local stub cache using the command pattern" around the water cooler.

In the next, and final, article in this series, I'll address the problem of type safety. After discussing the newly-defined generics extension to the Java programming language, I'll show how it partially addresses this problem by allowing us to return strongly typed values (instead of instances of Object) and slightly narrow our use of Exception.

William Grosso is a coauthor of Java Enterprise Best Practices.


Return to ONJava.com.