Friday, February 15, 2008

Memcached as Hibernate second cache provider - implementation 2

Here is the implementation based on another java memcached client library: http://bleu.west.spy.net/~dustin/projects/memcached/

I tested it on my development machine, it works. But I have not do any stress test to compare this implementation and the previous implement. If anybody is going to do it, please post the result here.

Here is the code for the two classes: provider and cache.

/**
* Memcached plugin for Hibernate
* configuration (hibernate.cfg.xml)
*
&lt pproperty name="cache.provider_class" &gt MemcachedHibernateCacheProvider &lt /pproperty &gt
&lt pproperty name="memcached.servers" &gt 192.168.1.2:11211,192.168.1.3:11211 &lt /pproperty &gt
&lt pproperty name="memcached.failover" &gt true &lt /pproperty &gt
&lt pproperty name="memcached.initConn" &gt 10 &lt /pproperty &gt
&lt pproperty name="memcached.minConn" &gt 10 &lt /pproperty &gt
&lt pproperty name="memcached.maxConn" &gt 1000 &lt /pproperty &gt
&lt pproperty name="memcached.maintSleep" &gt 30 &lt /pproperty &gt
&lt pproperty name="memcached.nagle" &gt false &lt /pproperty &gt
&lt pproperty name="memcached.socketTO" &gt 3000 &lt /pproperty &gt
&lt pproperty name="memcached.aliveCheck" &gt true &lt /pproperty &gt
&lt pproperty name="memcached.[cache_name_as_full_classname]" &gt 10s|20m|30h|40d &lt /pproperty &gt



*
*/

public class MemcachedHibernateCacheProvider2 implements CacheProvider {

private static final Log log = LogFactory.getLog(MemcachedHibernateCacheProvider2.class);

public final static String DEFAULT_CACHE_NAME="____DEFAULT_CACHE_REGION";
public static String serverlist;

private Hashtable cacheManager = new Hashtable();

/* (non-Javadoc)
* @see org.hibernate.cache.CacheProvider#start(java.util.Properties)
*/
public void start(Properties props) throws CacheException {

String servers = props.getProperty("memcached.servers");
//log.debug("*** --- *** server is "+ servers);
serverlist = servers.replace(',', ' ');
log.debug("*** --- *** server is "+ serverlist);
//System.out.println("*** --- *** server is "+ serverlist);

//if(StringUtils.isBlank(servers)){
if(servers==null || servers.trim().length()==0) {
throw new CacheException("configuration 'memcached.servers' get a empty value");
}

}

/* (non-Javadoc)
* @see org.hibernate.cache.CacheProvider#buildCache(java.lang.String, java.util.Properties)
*/
public Cache buildCache(String cacheName, Properties props) throws CacheException {
if((cacheName==null || cacheName.trim().length()==0))
cacheName = DEFAULT_CACHE_NAME;
MemcachedCache2 mCache = cacheManager.get(cacheName);
if(mCache == null){
String timeToLive = props.getProperty("memcached."+cacheName);
int secondToLive = -1;
//if(StringUtils.isNotBlank(timeToLive)){
if(timeToLive!=null && timeToLive.trim().length()>0){
timeToLive = timeToLive.toLowerCase().trim();
secondToLive = getSeconds(timeToLive);
}
System.out.println("=== Building cache named "+cacheName+" using secondToLive is "+secondToLive);
try{
mCache = new MemcachedCache2(serverlist, cacheName, secondToLive);
cacheManager.put(cacheName, mCache);
}catch(Exception e){
throw new CacheException(e);
//cacheManager.put(cacheName, cacheManager.get(DEFAULT_CACHE_NAME));
}
}
return mCache;
}

private static int getSeconds(String str){
try{
switch(str.charAt(str.length()-1)){
case 's':
return Integer.parseInt(str.substring(0, str.length()-1));
case 'm':
return Integer.parseInt(str.substring(0, str.length()-1)) * 60;
case 'h':
return Integer.parseInt(str.substring(0, str.length()-1)) * 3600;
case 'd':
return Integer.parseInt(str.substring(0, str.length()-1)) * 86400;
default:
return Integer.parseInt(str);
}
}catch(NumberFormatException e){
log.warn("Illegal configuration value : " + str, e);
}
return -1;
}


/* (non-Javadoc)
* @see org.hibernate.cache.CacheProvider#stop()
*/
public void stop() {
}

/* (non-Javadoc)
* @see org.hibernate.cache.CacheProvider#isMinimalPutsEnabledByDefault()
*/
public boolean isMinimalPutsEnabledByDefault() {
return false;
}

/* (non-Javadoc)
* @see org.hibernate.cache.CacheProvider#nextTimestamp()
*/
public long nextTimestamp() {
return Timestamper.next();
}
}

----------------------------------------------------------------------------------------

/**
* Cache plugin for Hibernate
* @author Hank Li
*/
public class MemcachedCache2 implements Cache {

private static final Log log = LogFactory.getLog(MemcachedCache2.class);

private static final int SIXTY_THOUSAND_MS = 60000;

private MemcachedClient mc;
private int secondToLive;
private String cacheName;

/**
* Creates a new Hibernate pluggable cache based on a cache name.
*
*/
public MemcachedCache2(String serverlist, String name, int secondToLive)
throws IOException {
mc = new MemcachedClient(AddrUtil.getAddresses(serverlist));
this.secondToLive = secondToLive;
this.cacheName = name + '_';
}

private String makeupKey(Object key) {
return cacheName + String.valueOf(key).hashCode();
}

/**
* Gets a value of an element which matches the given key.
*
* @param key
* the key of the element to return.
* @return The value placed into the cache with an earlier put, or null if
* not found or expired
* @throws CacheException
*/
public Object get(Object key) throws CacheException {
//log.debug("key: " + key);
System.out.println("*** key: " + key);
if (key == null) {
return null;
} else {
//Object rt = mc.get(makeupKey(key));

// Try to get a value, for up to 2 seconds, and cancel if it doesn't return
Object rt=null;
//synchronized way
try {
rt=mc.get(makeupKey(key));

}catch(Exception alle){
System.out.println("memcached exception"+alle);
throw new CacheException(alle);
}
/* asynchronized way
java.util.concurrent.Future f=mc.asyncGet(makeupKey(key));


try {
rt=f.get(2, TimeUnit.SECONDS);
//log.debug("future object="+f.toString());
//log.debug("rt is "+rt);
//rt=f.get();
} catch(TimeoutException e) {
// Since we don't need this, go ahead and cancel the operation. This
// is not strictly necessary, but it'll save some work on the server.
log.error("timeout excpetion", e);
f.cancel(false);
// Do other timeout related stuff
}catch(Exception alle){
log.error("memcached exception", alle);
}
*/
if (rt == null) {
log.debug("Element for " + key + " is null");
//System.out.println("--- Element for " + key + " is null");
}
return rt;

}

}

public Object read(Object key) throws CacheException {
return get((key));
}

/**
* Puts an object into the cache.
*
* @param key
* a key
* @param value
* a value
* @throws CacheException
* if the {@link CacheManager} is shutdown or another
* {@link Exception} occurs.
*/
public void update(Object key, Object value) throws CacheException {
put((key), value);
}

/**
* Puts an object into the cache.
*
* @param key
* a key
* @param value
* a value
* @throws CacheException
* if the {@link CacheManager} is shutdown or another
* {@link Exception} occurs.
*/
public void put(Object key, Object value) throws CacheException {

mc.set(makeupKey(key), secondToLive, value);

}

/**
* Removes the element which matches the key.

If no element matches,
* nothing is removed and no Exception is thrown.
*
* @param key
* the key of the element to remove
* @throws CacheException
*/
public void remove(Object key) throws CacheException {
mc.delete(makeupKey(key));
}

/**
* Remove all elements in the cache, but leave the cache in a useable state.
*
* @throws CacheException
*/
public void clear() throws CacheException {
mc.flush();
}

/**
* Remove the cache and make it unusable.
*
* @throws CacheException
*/
public void destroy() throws CacheException {

}

/**
* Calls to this method should perform there own synchronization. It is
* provided for distributed caches. Because EHCache is not distributed this
* method does nothing.
*/
public void lock(Object key) throws CacheException {
}

/**
* Calls to this method should perform there own synchronization. It is
* provided for distributed caches. Because EHCache is not distributed this
* method does nothing.
*/
public void unlock(Object key) throws CacheException {
}

/**
* Gets the next timestamp;
*/
public long nextTimestamp() {
return Timestamper.next();
}

/**
* Returns the lock timeout for this cache.
*/
public int getTimeout() {
// 60 second lock timeout
return Timestamper.ONE_MS * SIXTY_THOUSAND_MS;
}

public String getRegionName() {
return null;
}

/**
* Warning: This method can be very expensive to run. Allow approximately 1
* second per 1MB of entries. Running this method could create liveness
* problems because the object lock is held for a long period


*
* @return the approximate size of memory ehcache is using for the
* MemoryStore for this cache
*/
public long getSizeInMemory() {
log.warn("cann't getSizeInMemory in memcached!");
throw new CacheException("cann't getSizeInMemory in memcached!");
}

public long getElementCountInMemory() {
log.warn("cann't getElementCountInMemory in memcached!");
throw new CacheException("cann't getElementCountInMemory in memcached!");
}

public long getElementCountOnDisk() {
log.warn("cann't getElementCountOnDisk in memcached!");
throw new CacheException("cann't getElementCountOnDisk in memcached!");
}

public Map toMap() {
log.warn("cann't toMap in memcached!");
throw new CacheException("cann't toMap in memcached!");
}

public String toString() {
return "MemCached(" + getRegionName() + ')';
}
}



No comments: