Thursday, September 15, 2011

Using Ehcache in a distributed environment

In this blog I will show you how to use Ehcache to share data in a distributed environment. My suggestion is to start with a small app and get it running in a single node environment. Once the correct behavior of your app is verified, you can simply change Ehcache config to use RMI replicated caching and deploy your app to multiple nodes.

Lets start with a simple application in Java.

Class Data.java encapsulates the information we want cached. Notice this class needs to implement java.io.Serializable.


package com.mycompany.myproject.mypackage;

import java.io.Serializable;

public class Data implements Serializable {

private static final long serialVersionUID = 2725776132167576714L;
private String id;
private String name;

public Data(String id, String name) {
this.id = id;
this.name = name;
}

public Data(String name) {
this.name = name;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public int hashCode() {
int result = 0;
/* add code here */
return result;
}

@Override
public boolean equals(Object obj) {
/* add code here */
return true;
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Data [id=");
builder.append(id);
builder.append(", name=");
builder.append(name);
builder.append("]");
return builder.toString();
}
}


You need to configure caching and let Ehcache handle your Data cache.

Ehcache works off of a configuration file in XML. By default if ehcache.xml is found on your classpath it will be used. Later on I will show you how to configure Ehcache without using "ehcache.xml".

For now, lets assume you are working with ehcache.xml. If you are using a Maven project, simply place this file in your src/main/resources.

ehcache.xml should look like this:


<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false">

<diskStore path="java.io.tmpdir/EhCacheNbWeb" />

<defaultCache eternal="false" maxElementsInMemory="1000"
overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0"
timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU" statistics="true" />

<cache name="dataCache" eternal="true"
maxElementsInMemory="100" overflowToDisk="false" diskPersistent="false"
timeToIdleSeconds="0" timeToLiveSeconds="300"
memoryStoreEvictionPolicy="LRU" statistics="true" />

</ehcache>



To initialize and start using the cached data, we need to create a class called DataCacheManager.



package com.mycompany.myproject.cache;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;

public class DataCacheManager {

private String[] cacheNames;
private CacheManager cacheMgr;
private Cache dataCache;

public DataCacheManager() {
cacheMgr = CacheManager.create();
cacheNames = CacheManager.getInstance().getCacheNames();
dataCache = cacheMgr.getCache("dataCache");
}

public void getCache() {
dataCache = cacheMgr.getCache("dataCache");
}

public void addData(Data data) {
Element dataElement = new Element(data.getId(), data);
dataCache.put(dataElement);
}

public Data getData(String id) {
Element dataElement = dataCache.get(id);
return (Data)dataElement.getValue();
}

public boolean deleteData(String id) {
return dataCache.remove(id);
}

public ArrayList getAllData() {
List keys = dataCache.getKeys();
Iterator iterator = keys.iterator();
ArrayList dataList = new ArrayList();
String key;
while (iterator.hasNext()) {
key = (String) iterator.next();
dataList.add(getData(key));
}
return dataList;
}

public Data addData(String id, String name) {
Data data = new Data(id, name);
addData(data);
return data;
}

public void deleteAllData() {
dataCache.dispose();
}

public String getUniqueDataId() {
UUID uuid = UUID.randomUUID();
return String.valueOf(uuid);
}

public static void main(String args[]) {
DataCacheManager dataCacheMgr = new DataCacheManager();
System.out.print("cacheNames = ");
for (int i = 0; i < dataCacheMgr.cacheNames.length; i++)
System.out.print(" " + dataCacheMgr.cacheNames[i]);
Data data = new Data("myUniqueId_123", "myName");
dataCacheMgr.addData(data);
Data data2 = dataCacheMgr.getData("myUniqueId_123");
dataCacheMgr.deleteData("myUniqueId_123");
}
}



To run your application in a cluster of nodes and distribute it across multiple nodes, follow these steps:

Lets assume there are two nodes in your cluster.
1. Deploy your application to both nodes.
2. Modify your ehcache.xml as in the example below and place is on the classpath of each node.



<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">

<cacheManagerEventListenerFactory class="" properties="" />

<!-- Server 1 -->
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=manual, rmiUrls=//10.66.77.99:40001/dataCache" />

<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="hostName=10.66.77.88, port=40001, socketTimeoutMillis=120000" />

<!-- Server 2 -->
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=manual, rmiUrls=//10.66.77.88:40001/dataCache" />

<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="hostName=10.66.77.99, port=40001, socketTimeoutMillis=120000" />

<defaultCache eternal="true" maxElementsInMemory="100"
overflowToDisk="false" />

<cache name="dataCache" maxElementsInMemory="100"
eternal="true" timeToIdleSeconds="0" timeToLiveSeconds="0"
overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true,
replicateUpdatesViaCopy=false, replicateRemovals=true " />

<bootstrapCacheLoaderFactory class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory" />
</cache>
</ehcache>

8 comments:

  1. Noushin,

    I have setup above example on 2 physical machines with said configuration and same example but does not replicate data?

    Can I know the reason or I am missing anything

    pls give me your email address so I can share my code

    ReplyDelete
  2. I have copied same example is running on 2 machine but not replicating data?

    ReplyDelete
    Replies
    1. Are you getting an exception? Do you know if the two nodes are accessible to each other?

      Delete
  3. HI i am gettin g this exception when use the configuration suggested by you WARN [net.sf.ehcache.distribution.RMIAsynchronousCacheReplicator]: Unable to send message to remote peer. Message was: RemoteException occurred in server thread; nested exception is:
    java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
    java.lang.ClassNotFoundException: com.ehcache.nagarro.model.Message (no security manager: RMI class loader disabled)
    java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
    java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
    java.lang.ClassNotFoundException: com.ehcache.nagarro.model.Message (no security manager: RMI class loader disabled)

    ReplyDelete
  4. This exception come when ur model/entity is not implementing serializable interface

    ReplyDelete
  5. This exception come when ur model/entity is not implementing serializable interface

    ReplyDelete
  6. Thank you nice article. I have question on distribute cache. Do we need any other third party jars such as Terra Cotta for distributed caching. Or whatever default artifacts come from ehache are enough for distributed chaching.

    ReplyDelete