Thursday, August 6, 2009

JCS: Java Caching System

Many Web applications are being rewritten from desktop applications; ideally, they should be as fast and scalable as the desktop versions. Almost all Web applications could benefit from a substantial increase in speed. Caching of frequently viewed data that changes infrequently is a powerful way to decrease wait time for users. A utility that easily handles the caching of data with a simple, nonintrusive API can help you accomplish this goal. The open source JCS, an Apache Jakarta project, is one such tool. This article explains how to configure and use JCS to cache data for your Web applications.

JCS overview

JCS is a caching system written in the Java language that you can use to create Java desktop and Web applications. It provides convenient mechanisms for storing data in the cache, getting data from the cache, removing data from the cache, and much more.

With JCS, you can store cached data in different data regions that you specify. JCS defines four types of core regions: memory, disk, lateral, and remote. You can use the core regions together to gain great flexibility in how and where you store your cache data. You can specify which region should be used first and when to fail over to another region.

Memory region

The memory region is a pure-memory cache region that uses a Least Recently Used (LRU) algorithm. When the memory cache region becomes full, LRU removes the least recently used cached data first. This data region performs well, and most JCS users designate it as the default cache region to use first.

JCS's pluggable controller

JCS makes using multiple regions for cache storage simple through its Composite Cache. The Composite Cache provides a pluggable controller for a cache region. The Composite Cache takes care of the difficult task of deciding when and how to use each cache region. You as a developer only need to worry about getting and setting the cache. JCS does most of the hard work.

Disk region

The disk region is a region for cache data on the Web server's file disk. To improve performance, JCS stores the cached data keys in memory while storing the actual cached data on the file disk. In a typical JCS configuration that uses the memory region first, any data that can't be held in the memory region is then written to the disk region.

Lateral region

The lateral cache region provides a configurable way to distribute the cached data across multiple servers. The cached data servers must have a port open to listen on, and a socket connection must be created. This region does face a potential problem because the region does not guarantee data consistency between caches. But if the region is used as designed, then this problem is unlikely to occur.

Remote region

The remote region provides a cache region using a remote method invocation (RMI) API. The region uses a remote server that handles the cached data. The remote cached server can be used by multiple JCS client applications to store the cached data. Listeners are defined to collect the requests from the clients and the servers. This cache region helps to alleviate overhead with serialization and multiple connection points.


JCS configuration

Configuring JCS is as simple as creating and populating a cache.ccf file. This file defines which regions your cache should use and the attributes or options of those regions. Tailoring the file to your application's needs is a convenient way to scale your cache quickly. I've made the following examples as simple as possible in order to display the main configuration points. You can specify a number of options and attributes to suit the configuration to exactly what you need.

Listing 1 displays the most basic cache.ccf file — a pure memory-cache configuration:


Listing 1. Basic configuration for JCS

jcs.default=jcs.default.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.default.cacheattributes.MaxObjects=1000
jcs.default.cacheattributes.MemoryCacheName=
org.apache.jcs.engine.memory.lru.LRUMemoryCache

You can see from the last line in Listing 1 that the configuration file specifies the memory cache as a LRUMemoryCache. You can also see that the maximum number of objects kept in memory is set to 1000.

Most applications would have a more comprehensive configuration for their caching system than the one in Listing 1. In the configuration in Listing 2, I use a memory region and a disk region while defining my own region (OUR_REGION):


Listing 2. Regions defined in the configuration for JCS
jcs.default=DISK_REGION
jcs.default.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.default.cacheattributes.MaxObjects=1000
jcs.default.cacheattributes.MemoryCacheName=
org.apache.jcs.engine.memory.lru.LRUMemoryCache

jcs.region.OUR_REGION=DISK_REGION
jcs.region.OUR_REGION.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.region.OUR_REGION.cacheattributes.MaxObjects=1000
jcs.region.OUR_REGION.cacheattributes.MemoryCacheName=
org.apache.jcs.engine.memory.lru.LRUMemoryCache
jcs.region.OUR_REGION.cacheattributes.UseMemoryShrinker=true
jcs.region.OUR_REGION.cacheattributes.MaxMemoryIdleTimeSeconds=3600
jcs.region.OUR_REGION.cacheattributes.ShrinkerIntervalSeconds=60
jcs.region.OUR_REGION.cacheattributes.MaxSpoolPerRun=500
jcs.region.OUR_REGION.elementattributes=org.apache.jcs.engine.ElementAttributes
jcs.region.OUR_REGION.elementattributes.IsEternal=false

jcs.auxiliary.DISK_REGION=org.apache.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
jcs.auxiliary.DISK_REGION.attributes=
org.apache.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
jcs.auxiliary.DISK_REGION.attributes.DiskPath=c:/jcs/disk_region
jcs.auxiliary.DISK_REGION.attributes.maxKeySize=100000

JCS auxiliaries

In addition to the four core cache implementations, JCS provides auxiliaries, which are optional plug-ins that a region can use. The auxiliaries include the Indexed Disk Cache, the TCP Lateral Cache, and the Remote Cache Server. The Disk Cache, for example, allows you to swap items onto disk when a memory threshold is reached. This allows each region to control its caching on a more flexible level, providing a rough analogy to the virtual memory approach used by most operating systems. The cache.ccf configuration file allows you to tailor each auxiliary region to your application's needs.

The first line in Listing 2 shows that the configuration is setting the default region to the DISK_REGION. The DISK_REGION is of type IndexedDiskCacheFactory, and the file is specified on the disk as c:\jcs\disk_region. The second configuration group in Listing 2 defines my own region, for which I added some options. This type of configuration — one that uses both the memory region and a disk region while specifying a user-defined region — is a common one. The third configuration group in Listing 2 defines an auxiliary region.

JCS has two dependencies: concurrent and commons-logging. (Prior to JCS version 1.2.7.0, there were two additional dependencies: commons-collections and commons-lang.)


Basic JCS usage

A good way to learn the basics of JCS is to view the API's most commonly used methods. The best place to start is with the initialization of the cache region itself. Initializing the JCS cache region object gives you access to almost all of the common methods you'll need. Listing 3 initializes the JCS object and gets an instance of the default cache region:


Listing 3. Retrieving the default cache region
// Initialize the JCS object and get an instance of the default cache region
JCS cache = JCS.getInstance("default");

After retrieving the JCS instance, you can invoke the most needed methods. The put method places a new object into the cache. All that is needed is a key (first parameter) and a value (second parameter). Listing 4 shows a basic example:


Listing 4. Setting a cached item

// Set up
String key = "key0";
String value = "value0";

// Place a new object in the cache
cache.put(key, value);

The example in Listing 4 uses string values for the parameters, but you can use any object.

Retrieving a cached object is as simple as using the get method provided by JCS. Listing 5 shows a simple example. Again, the example uses a string parameter, but you can use any object.


Listing 5. Retrieving a cached item
// Retrieve a cached object
String cachedData = (String)cache.get(key);

A test for cache data validity is an additional method you might need when dealing with a caching system. In JCS, no test cache method is defined for simply testing for a cached item's existence. Instead, the get method's return value can help. Listing 6 shows a way to achieve this necessary functionality:


Listing 6. Testing a cached item for validity
// Retrieve a cached object
String cachedData = (String)cache.get(key);

// Check if the retrieval worked
if (cachedData != null) {
// The cachedData is valid and can be used
System.out.println("Valid cached Data: " + cachedData);
}

The last of the more common cache utilities you might need are for cleaning up JCS, the cached items, and the cache region after you are finished working with them. JCS provides a clear method for removing all cached data from the cache region that it was called with and a remove method for removing a specific cached item. A dispose method is also available to dispose of the JCS region that was initialized. Listing 7 shows how to use these methods:


Listing 7. Cleaning up the cache region
// Dispose of a specific cached item
cache.remove(key);

// Dispose of all cache data
cache.clear();

// Dispose of the cache region
cache.dispose();


JCS and Java objects

One of the advantages of JCS over other caching systems (see Resources) is that it works well with objects. Most Web applications created with Java technology use an object-oriented approach. Caching of objects helps your application perform much better than it would if it continually retrieved objects piece by piece from a database, for example.

The first step in designing a simple object-oriented site around JCS starts with the object you need to store. For this example, I'll develop a basic blogging site, so I'll store the blog object itself. Listing 8 shows the BlogObject class I'll use:


Listing 8. BlogObject class
package com.ibm.developerWorks.objects;

import java.io.Serializable;
import java.util.Date;

public class BlogObject implements Serializable {
private static final long serialVersionUID = 6392376146163510046L;
private int blogId;
private String author;
private Date date;
private String title;
private String content;

public BlogObject(int blogId, String author, Date date, String title, String content) {
this.blogId = blogId;
this.author = author;
this.date = date;
this.title = title;
this.content = content;
}

public int getBlogId() {
return this.blogId;
}

public String getAuthor() {
return this.author;
}

public Date getDate() {
return this.date;
}

public String getTitle() {
return this.title;
}

public String getContent() {
return this.content;
}
}

Once you have the object represented in a class, you next need a class to manage it. The manager handles all administrative aspects and caching functionality related to the blog objects. The manager, in this example, will handle three main tasks:

  • Retrieving blog objects
  • Setting blog objects in the cache
  • Cleaning blog objects from the cache

The getBlog method, shown in Listing 9, retrieves the blog object. The method first tries to attain the blog object from the cache. If the object is not in the cache, it will get it from another mechanism:


Listing 9. Retrieving a blog object via the blog manager
public BlogObject getBlog(int id) {
BlogObject blog = null;

try {
blogCache = JCS.getInstance(blogCacheRegion);
blog = (BlogObject)blogCache.get(id);
} catch (CacheException ce) {
blog = null;
}

if (blog == null) {
blog = DatabaseManager.getBlog(id);
this.setBlog(
blog.getBlogId(),
blog.getAuthor(),
blog.getDate(),
blog.getTitle(),
blog.getContent()
);
}

return blog;
}

In Listing 9, I use a database as the alternative mechanism for retrieving the object. When you retrieve the object from another mechanism, you should set that object to the cache so that the next retrieval can get it directly from the cache.

The setBlog method, shown in Listing 10, places the blog object in the cache. This method is trivial because it simply creates a new blog object with the information passed in and then places it in the cache.


Listing 10. Placing a blog object in the cache via the blog manager
public boolean setBlog(int bId, String author, Date date, String title, String content) {
BlogObject blog = new BlogObject(bId, author, date, title, content);

try {
blogCache = JCS.getInstance(blogCacheRegion);
blogCache.put(bId, blog);
return true;
} catch (CacheException ce) {
return false;
}
}

The cleanBlog method, shown in Listing 11, cleans either one specified blog or cleans all of the blogs out of the cache. The method uses JCS's remove and clear methods to clean up the cache objects.


Listing 11. Removing blog objects from the cache via the blog manager
public boolean cleanBlog(int blogId) {
try {
blogCache = JCS.getInstance(blogCacheRegion);
blogCache.remove(blogId);
} catch (CacheException ce) {
return false;
}
return true;
}

public boolean cleanBlog() {
try {
blogCache = JCS.getInstance(blogCacheRegion);
blogCache.clear();
} catch (CacheException ce) {
return false;
}
return true;
}

The previous classes demonstrate how straightforward caching objects with JCS is. With a simple representation of the object and a manager for that object, you have a powerful yet simple way to handle objects in your Web application.


Cache metadata

JCS offers a great deal more than the basics I've shown you so far for adding caching to your applications. For example, it provides utilities for gathering metadata for cached objects and cache regions. You can easily retrieve:

  • The cache key name
  • The time the cached item was created
  • The maximum time that the cache will live
  • How much time until the cached item will expire

The example in Listing 12 shows how to retrieve metadata for cached items:


Listing 12. Retrieving metadata for cached items
try {
JCSAdminBean admin = new JCSAdminBean();
LinkedList linkedList = admin.buildElementInfo(regionName);
ListIterator iterator = linkedList.listIterator();

while (iterator.hasNext()) {
CacheElementInfo info = (CacheElementInfo)iterator.next();
System.out.println("Key: " + info.getKey());
System.out.println("Creation Time: " + info.getCreateTime());
System.out.println("Maximum Life (seconds): " + info.getMaxLifeSeconds());
System.out.println("Expires in (seconds): " + info.getExpiresInSeconds());
}
} catch (Exception e) {
}

Metadata for the cached items is useful, but it is also helpful to get metadata for each cache region. This information can let you know how much cached data is going into which region, including cache misses, cache hits, and cache updates. The example in Listing 13 shows how to gather this information:


Listing 13. Retrieving metadata for cache regions
try {
JCSAdminBean admin = new JCSAdminBean();
LinkedList linkedList = admin.buildCacheInfo();
ListIterator iterator = linkedList.listIterator();

while (iterator.hasNext()) {
CacheRegionInfo info = (CacheRegionInfo)iterator.next();
CompositeCache compCache = info.getCache();
System.out.println("Cache Name: " + compCache.getCacheName());
System.out.println("Cache Type: " + compCache.getCacheType());
System.out.println("Cache Misses (not found): " + compCache.getMissCountNotFound());
System.out.println("Cache Misses (expired): " + compCache.getMissCountExpired());
System.out.println("Cache Hits (memory): " + compCache.getHitCountRam());
System.out.println("Cache Updates: " + compCache.getUpdateCount());
}
} catch (Exception e) {
}

Gathering metadata for cache regions and items helps you analyze which areas and items of your Web site need to be optimized. Metadata also helps you manage time-sensitive cached data. For example, you could use the maximum life and expiration time for each cached item to refresh cached data for specific users that need updated data.


Conclusion

JCS is a powerful and yet simple-to-use caching system for Java developers. It provides data caching for desktop and Web applications alike. The growth of desktop-like Web applications is requiring increased Web application speed and agility. Caching of data can aid in this effort. This overview has shown how to configure and use JCS. I have also covered the syntax needed for basic caching methods, caching of objects used in a common Web application, and the retrieval of cache metadata. You now have the initial knowledge of JCS to start developing your next Web site quickly with the power of data caching. You might also want to study several additional JCS areas that provide advanced functionality, such as HTTP Servlet access, JCS utlities, basic HTTP authentication, and additional auxiliary regions.


Resources

Learn

No comments:

Post a Comment