Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Use Memcached for Java enterprise performance, Part 2: Database-driven web apps

Using Memcached with Hibernate in Java web applications

  • Print
  • Feedback

However you slice it traditional caching requires performance trade-offs that some enterprise applications cannot afford. Find out for yourself why Memcached is a go-to solution for Java developers whose applications need serious scale. After first setting up spymemcached as your open source Java client for Memcached, you'll use it in two powerful application scenarios: first configuring Memcached as second-level cache for Hibernate (via hibernate-memcached), and then using it to cache the HTML generated for each web page.

We concluded the first half of this tutorial with a look at using Telnet and the Memcached protocol to store and retrieve cache entries in a Memcached server. Accessing Memcached via Telnet is especially useful for debugging, but if you want to use Memcached in a Java enterprise application you'll need to use a Memcached Java client.

We'll use spymemcached, a very popular Memcached Java client, for the introductory purposes of this tutorial. Listing 1 shows spymemcached's main class, MemcachedClient.

Listing 1. MemcachedClient

public static void main(String[] args) throws Exception{
    if(args.length < 2){
        System.out.println("Please specify command line options");
        return;
    }
    MemcachedClient memcachedClient = new MemcachedClient(AddrUtil.getAddresses("127.0.0.1:11211"));
    if(commandName.equals("get")){
        String keyName= args[1];
        System.out.println("Key Name " +keyName);
        System.out.println("Value of key " +memcachedClient.get(keyName));
    }else if(commandName.equals("set")){
        String keyName =args[1];
        String value=args[2];
        System.out.println("Key Name " +keyName + " value=" + value);
        Future<Boolean> result= memcachedClient.set(keyName, 0, value);
        System.out.println("Result of set " + result.get());
    }else if(commandName.equals("add")){
        String keyName =args[1];
        String value=args[2];
        System.out.println("Key Name " +keyName + " value=" + value);
        Future<Boolean> result= memcachedClient.add(keyName, 0, value);
        System.out.println("Result of add " + result.get());
    }else if(commandName.equals("replace")){
        String keyName =args[1];
        String value=args[2];
        System.out.println("Key Name " +keyName + " value=" + value);
        Future<Boolean> result= memcachedClient.replace(keyName, 0, value);
        System.out.println("Result of replace " + result.get());
    }else if(commandName.equals("delete")){
        String keyName =args[1];
        System.out.println("Key Name " +keyName );
        Future<Boolean> result= memcachedClient.delete(keyName);
        System.out.println("Result of delete " + result.get());
    }else{
        System.out.println("Command not found");
    }
    memcachedClient.shutdown();

}

In Listing 1, we first create a MemcachedClient object with hostname:portname as its argument. Once we have the object we can start calling its methods to set and get cache entries. Note that MemcachedClient has equivalent methods for every method supported by the Memcached protocol:

  • MemcachedClient.set() is used to store a Java object into the Memcached server. In Listing 1, we stored an instance of a Contact object with the string contactId-1. The second parameter of this set method is time-in-seconds.
  • MemcachedClient.get() is used to get the value of the key from a Memcached server. In Listing 1 the value of the key is an object of Contact.java, so the client will first retrieve the value and then deserialize it and return the object. The get() method returns null if the value is not found or is expired.
  • MemcachedClient.add() is used to add an object to the cache only if it does not already exist. In Listing 1 the contactId-1 key exists in the cache already, so it won't be added.
  • MemcachedClient.replace() replaces an object with the value for the given key (if there is already such a value). In Listing 1, contactId-1 is already in the cache, so its value would be replaced.
  • MemcachedClient.delete() deletes a given key from the cache. In Listing 1, the call is used to delete the contactId-1 key.

MemcachedClient.set()

You can use MemcachedClient.set() to store a simple string or a complex object. When you store a complex object, MemcachedClient will first serialize the object and then store it. As a result, every object that you store in Memcached must be serializable, and the key must also be a string. In Listing 1, the set() method returns an object of Future<Boolean>. When we call the set method, it is executed asynchronously, so the control moves to the next line without waiting for a response from the Memcached server. If you needed to know the result of the set operation, then you would call setResult.get() on the Future object instance.

Spying under the hood: spymemcached

Before we move on to the web application exercise, let's look under the hood to see how spymemcached works. Figure 1 is a sequence diagram showing what happens in spymemcached when a client issues a get().

Figure 1. get() under the hood (click to enlarge)

 

Spymemcached is an asynchronous, single-threaded Memcached client. When you call any caching-related method on spymemcached's MemcachedClient, it will be handled asynchronously. The client call method handles writing the details of the operation that should be performed into a queue and returning the control back to the client making the call. The actual interaction with the Memcached server, meanwhile, is handled by a separate thread that runs in the background.

Notice that the sequence diagram in Figure 1 has two different threads. The first thread shows the method sequence for what happens when a client makes a get() call. The second thread displays a method sequence for the daemon thread that communicates with the Memcached server. Both threads are worth a closer look.

Sequence of events in a client thread

When you call MemcachedClient's get() method it takes the arguments and forwards control to the asyncGet() method on the object of Memcached class. The asyncGet() method then forwards control to either AsciiOperationFactory or BinaryOperationFactory, depending on the Memcached protocol your client uses to communicate with the server. AsciiOperationFactory is the default value.

The AsciiOperationFactory constructs an object of the command-specific operation object. In this case, since the client issued a get command, it creates an object of GetOperationImpl and returns it. The MemcachedClient.asyncGet() method then takes care of attaching a callback function to the operation. This function will be called when MemcachedClient gets data back from the server and returns a java.util.concurrent.Future object. The client uses the java.util.concurrent.Future to retrieve the data returned from server.

Once MemcachedClient has the object of GetOperationImpl, it first tries to validate the key by ensuring that the length of the key is less than 250 characters and does not contain any special characters. Once the key is validated, the next task is to figure out which server the request should go to. For that MemcachedClient passes control to an instance of the NodeLocator class with the key. The NodeLocator class calls the HashAlgorithm.hash(key) method to get the hashCode for the key. By default, NodeLocator will call the hashCode() method on the key, which is a String object. Once it has the hashCode it will divide that by the number of servers; for example, if the hashCode were 10 and the number of servers three, then the remainder would be one. So the cache entry would be located in Server 1. The MemcachedNode object representing Server 1 would be selected. The MemcachedConnection object would then add the get operation to the queue of Server 1 and return control to the client code.

  • Print
  • Feedback

Resources