Thursday, July 24, 2008

Caching Remote Stubs to Improve RMI

In a typical RMI-based distributed system, the client makes two remote calls to the server:
  1. To fetch a stub from the remote RMI registry, and
  2. To make a method call to the server using the stub

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 as processor speed is increasing at a faster rate than network speed).

Another problem you may see, especially in a very large distributed environment, is the "too many open files" error. This occurs if your client is making lots of quick RMI calls to lots of different remote servers and there isn't enough time for the opened RMI sockets to close. (I think sockets are closed if they have been idle for at least 15 seconds). As a result, you run out of "file descriptors" and aren't able to make any more remote calls, until some have freed up. This can be a pain and you have to get your Unix SA to increase the ulimit on the client host, in order make more remote connections.

In this post, I will describe how you can use a cache to halve the number of remote calls you make. So instead of fetching a new 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 you need to make a remote call, the stub is already available locally and, hence, only one remote method invocation is necessary. BUT the stub may not be valid (e.g. if the server has been restarted). If the stub isn't valid, an instance of RemoteException will be thrown when client attempts to use it to make a remote method call. In this case, the stub should be removed from the cache and a remote lookup performed to get a fresh one.

RemoteCache.java

import java.net.MalformedURLException;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Hashtable;
import java.util.Map;

/**
 * A local Remote stub cache
 * Instead of fetching stubs each time over the network,
 * simply fetch them the first time and store them in a
 * local cache.
 *
 * @author Fahd Shariff
 */
public class RemoteCache
{
  private static Map<String,Remote> cache
   = new Hashtable<String, Remote>();

  /**
   * Returns a stub to the remote object.
   * This method first checks the cache
   * and if not found, looks up in the registry
   *
   * @param serverDescription the url to the server
   * @return a stub to the remote object
   * @throws MalformedURLException
   * @throws RemoteException
   * @throws NotBoundException
   */
  public static Remote get(String serverDescription)
    throws MalformedURLException,
           RemoteException,
           NotBoundException {
    Remote toReturn=cache.get(serverDescription);
    if (toReturn==null) {
      toReturn=(Remote)RMIRegistry.lookup(serverDescription);
      if (toReturn!=null) {
        cache.put(serverDescription,toReturn);
      }
    }
    return toReturn;
  }

  /**
   * Removes the specified Remote Object from the cache.
   *
   * @param serverDescription
   */
  public static void remove(String serverDescription) {
    cache.remove(serverDescription);
  }

}
Using the RemoteCache
The following code snippet shows how you use the RemoteCache to obtain a stub. There is a simple retry loop - if a RemoteException occurs, the invalid stub is removed from the cache and a fresh one is obtained.
String url = "rmi://remotehostname:2138";
while(retries<MAX_RETRIES){
  try{
    MyRemoteObject remoteObj = (MyRemoteObject)
                           RemoteCache.get(url);
    remoteObj.callMyMethod();
    break;
  }
  catch(RemoteException e){
    //stub is invalid, so remove and retry
    RemoteCache.remove(url);
    retries++;
  }
}
References:
Seamlessly Caching Stubs for Improved Performance
Expiring Data with Hashbelts

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.