Showing posts with label hibernate. Show all posts
Showing posts with label hibernate. Show all posts

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() + ')';
}
}



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() + ')';
}
}

Tuesday, January 22, 2008

memcached, the best choice for distributed hibernate second cache

After painful evaluation of the terracotta, finally I decided to drop it.

The terracotta concept is good and attracting. But when I am testing, it causes us a lot of trouble for class loading.

After many workarounds, terracotta server is up and running. But the cpu is pretty high under load and GC time killed the application since responding time is the key for the web application.

Finally, I decide to write the memcached provider for the hibernate, use use memcached as the second level cache for hibernate. The client API I used is http://www.whalin.com/memcached/. I have not optimized the code yet, but the cpu and memory usage is much lower than terracotta server.

I also eveluate other java cache solutions, all of them have their advantages. But still none of them can bypass the limitation of heap size and will cause big GC time.

Compared to those java cache solution, memcached's get/set operation is slower, but comparing to the database operation, the difference of the get/set operations between memcached and ehcache can be ignored.

If there are more and more data to cache on the road, hibernate + memcached as second level cache is the way to go.

I am also doing research on the way to use ehcache as local and short term cache and use the memcached as the long term and distributed cache.

I will post more information about my memcached cache provider for hibernate.

Wednesday, October 10, 2007

which cache for the hibernate? memcached, EHCache or terracotta?

I am in the process to evaluate which cache is better for my purpose. I will post my research result here.

Greg Luck has done a rough test for memcached and EHCache.

http://gregluck.com/blog/archives/2007/05/comparing_memca.html

The data shows EHCache is much faster than memcached. I think the testing is not fair, since the memcached is for distributed cache and test for EHCache has tested the local cache only.