Showing posts with label RMI. Show all posts
Showing posts with label RMI. Show all posts

Tuesday, February 24, 2009

Sending Date Objects Over RMI

Had a funny issue with java Dates today which had me tearing my hair out: I was sending a java.sql.Date from one machine to another, via RMI and then storing it into the database, but the date being stored was incorrect!

The flow of the system can be described as follows:

  • CalcServer running on host A gets a "date-string" e.g. 20090220 and converts it to a java.sql.Date object using a SimpleDateFormat("yyyyMMdd")
  • CalcServer then sends the java.sql.Date object to the ResultServer, running on host B, via RMI.
  • ResultServer stores the java.sql.Date object to a java.sql.Types.DATE column in my Oracle database.

BUT, when I query my database I see 19-FEB-2009, instead of 20-FEB-2009! What is going on?

After a lot of investigation I found out that host A and host B were in different regions; one in Frankfurt and the other in London. The CalcServer in Frankfurt converts "20090220" into a date of "Fri Feb 20 00:00:00 CET 2009". When the ResultServer in London, deserialises the date it gets "Thu Feb 19 23:00:00 GMT 2009" and hence 19-FEB-2009 is stored in the database.

I have now fixed this by sending the date-string (not the java.sql.Date object) to the ResultServer, which will then parse it into a java.sql.Date object for storage.

Here is sample code which can be used to serialise a date on one host and deserialise it on another.

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class SerializationTest {

  public static void main(String[] args) throws Exception {

    final String yyyyMMdd = "20090220";
    final Date date = new SimpleDateFormat("yyyyMMdd").parse(yyyyMMdd);

    if (args.length != 1) {
      System.out.println("Usage SerializationTest S|D");
    }

    boolean serialise = false;
    if (args[0].equals("S")) {
      serialise = true;
    }
    else if (args[0].equals("D")) {
      serialise = false;
    }

    String filename = "date.ser";
    if (serialise) {
      // write the object to file
      FileOutputStream fos = new FileOutputStream(filename);
      BufferedOutputStream bos = new BufferedOutputStream(fos);
      ObjectOutputStream outputStream = new ObjectOutputStream(bos);
      outputStream.writeObject(date);
      outputStream.flush();
      outputStream.close();

      System.out.println("Serialised: " + date);
    }
    else {
      FileInputStream fis = new FileInputStream(filename);
      BufferedInputStream bis = new BufferedInputStream(fis);
      ObjectInputStream inputStream = new ObjectInputStream(bis);
      Date outDate = (Date) inputStream.readObject();
      inputStream.close();

      // print the object
      System.out.println(outDate);
    }
  }
}
Link to Stack Overflow Question

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