Friday, February 15, 2008

memcached hibernate cache provider

Here is the code to use memcached as hibernate second cache provider. So far it is working wonderful for about a month. I am planning to add more objects to the cache to test its limitation.

This provider is based on http://www.whalin.com/memcached/. Most part are from Chinese developer Liu Dong's job.

I also implemented the provider based on http://bleu.west.spy.net/~dustin/projects/memcached/. But I have not test it. I will post it in the next release let everybody to test it so that we can compare both libraries.

There are two classes here. the first one is MemcachedHibernateCacheProvider.java, the second one is the MemcachedCache.java

/**
* 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 MemcachedHibernateCacheProvider implements CacheProvider {

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

public final static String DEFAULT_CACHE_NAME="____DEFAULT_CACHE_REGION";

private Hashtable cacheManager = new Hashtable();

/* (non-Javadoc)
* @see org.hibernate.cache.CacheProvider#start(java.util.Properties)
*/
public void start(Properties props) throws CacheException {
SockIOPool pool = SockIOPool.getInstance();
String servers = props.getProperty("memcached.servers");
System.out.println("*** --- *** server is "+ servers);
log.info("*** --- *** server is "+ servers);
String[] serverlist = servers.split(",");
//if(StringUtils.isBlank(servers)){
if(servers==null || servers.trim().length()==0) {
throw new CacheException("configuration 'memcached.servers' get a empty value");
}
pool.setServers(serverlist);
pool.setFailover( Boolean.valueOf(props.getProperty("memcached.failover")) );
pool.setInitConn( Integer.valueOf(props.getProperty("memcached.initConn")) );
pool.setMinConn( Integer.valueOf(props.getProperty("memcached.minConn")) );
pool.setMaxConn( Integer.valueOf(props.getProperty("memcached.maxConn")) );
pool.setMaintSleep( Integer.valueOf(props.getProperty("memcached.maintSleep")) );
pool.setNagle( Boolean.valueOf(props.getProperty("memcached.nagle")) );
pool.setSocketTO( Integer.valueOf(props.getProperty("memcached.socketTO")) );
pool.setAliveCheck( Boolean.valueOf(props.getProperty("memcached.aliveCheck")) );
pool.initialize();
}

/* (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;
MemcachedCache mCache = cacheManager.get(cacheName);
if(mCache == null){
String timeToLive = props.getProperty("memcached."+cacheName);
int secondToLive = 3600 * 24 * 2; //default is 2 days
//if(StringUtils.isNotBlank(timeToLive)){
if(timeToLive!=null && timeToLive.trim().length()>0){
timeToLive = timeToLive.toLowerCase().trim();
secondToLive = getSeconds(timeToLive);
}
//System.out.println("=== Created cache "+cacheName+" with secondToLive="+secondToLive);
log.info("Created cache "+cacheName+" with secondToLive="+secondToLive);
mCache = new MemcachedCache(cacheName, secondToLive);
cacheManager.put(cacheName, mCache);
}
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();
}
}

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

public class MemcachedCache implements Cache {

private static final Log log = LogFactory.getLog(MemcachedCache.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 MemcachedCache(String name, int secondToLive) {
mc = new MemCachedClient();
mc.setCompressEnable(true);
mc.setCompressThreshold(4096);
this.secondToLive = secondToLive;
this.cacheName = name + '_';
}

private String makeupKey(Object key){
return cacheName + key.toString().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 {
//if (log.isDebugEnabled()) {
//log.debug("key: " + key);
//}
//System.out.println("*********get key: in cache " + cache_name + " is " + key);
if (key == null) {
return null;
} else {
Object rt = mc.get(makeupKey(key));
if (rt == null) {
//if (log.isDebugEnabled()) {
//log.debug("Element for " + key + " is null");
//System.out.println("-- Element for " + key + " is null");
//}
return null;
} else {
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 {
if(secondToLive <=0 ) mc.set(makeupKey(key), value); else{ Calendar cal = Calendar.getInstance(); cal.add(Calendar.SECOND, secondToLive); mc.set(makeupKey(key), value, cal.getTime()); } } /** * 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), null);
}

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

/**
* Remove the cache and make it unuseable.
*
* @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() + ')';
}
}

3 comments:

Anonymous said...

[B]NZBsRus.com[/B]
Escape Crawling Downloads Using NZB Files You Can Hastily Search HD Movies, Console Games, Music, Applications and Download Them at Blazing Speeds

[URL=http://www.nzbsrus.com][B]Usenet[/B][/URL]

Anonymous said...

You could easily be making money online in the underground world of [URL=http://www.www.blackhatmoneymaker.com]blackhat script[/URL], It's not a big surprise if you haven’t heard of it before. Blackhat marketing uses little-known or misunderstood ways to produce an income online.

Anonymous said...

Great blog post, been looking for something like that :)


http://ladybirdinsurance.info