Cache Distribuído com REST e EHCache Server no JBoss AS
As aplicalções que desenvolvemos hoje são cada vez mais complexas, esta complexidade se deve a muitos fatores que vão des da globalização até mesmo as evolução da forma como as empresas fazem B2B e B2C.
Muitas vezes o banco de dados acaba sendo um cargalo, uma vez que a sua rede esta 100% ok, uma forma viável de reduzir a carga no banco de dados é utilizar alguma forma de cache, se este cache tiver *hit*, ou seja, acertos em uma taxa considerável isso pode trazer muitas vantagens para a sua aplicação.
Cache com EHCache
EHCache é um solução open source de cache para Java. O EHCache lhe permite fazer vários tipos de cache como por exemplo aplicar cache junto ao Hibernate/JPA, cache de páginas web usando GZIP por exemplo e até mesmo cache distribuido que é o tema deste post.
Quando precisamos trabalhar com cache distribuído existem 2 opções com o EHCache. A primeira é utilizar o cluster do próprio Terracota(solução de cache distribuído da Terracota, empresa que comprou o EHCache :-) ) a outra opção é usar o cache Server.
O Cache Server tem um API REST muito bacana. O mais legal disso é que como REST funciona em cima de http você pode consumir o cache server com qualquer linguagen/técnologia, por exemplo você poderia ter cache distribuido para várias aplicações escritas em linguágens diferentes e rodando em diverentes sistemas operacionais e distribuidos pelo mundo.
Outro ponto a favor do Cache Server ser em REST é que com isso podemos usar as soluções de loadbalance e até mesmo DNS, isso por que ele vai ficar rodando em um servidor de aplicação parão JEE. Por default o REST Server vem empacotado em uma distribuição standalone que roda com um Glassfish embutido, neste post vou mostrar como adaptar o REST EHCache Server para rodar no JBoss AS 5.
Ajustes para Rodar no JBoss AS 5.x
Primeiro você precisa baixar o ehcache server, depois disso vamos deletar algumas bibliotecas e adicionar outras, esta é a unica modificação que precisamos fazer para rodar ele no JBoss AS. Neste post vou utilizar a versão 1.0.0.
Entre na pasta onde você estraiu o cache server, vou me referir a esta pasta como $Download. Depois entre na seguinte pasta: $Download/war/WEB-INF/lib e delete os seguintes jars:
Faça o deploy no JBoss AS 5.x e se divirta muito!
Manipulando o Servidor de Cache com REST
Agora vou mostrar como criar aréas de cache no REST Cache Server do EHCache e como adicionar e recuperar objetos deste cache, para isso vou utilizar dois frameworks que são o XStream e o HttpClient. Então vamos a classe genérica que criei para manipular o cache server chamei ele carinhosamente de HttpClientRESTBaby
Esta classe eu estou disponibilizando os seguintes métodos:
getCache: Retorna um XML no padrão EHCache com a configuração de cache que esta associada a esta área de cache no server.
addEntry: Adiciona um elemento ao cache, perceba que é uma conbinação de chave/valor, onde a chave deve ser única, a chave vai acabar sendo parte do URL REST e o valor deve ser Serializable, logo você pode sim passar um Objeto do seu dominio da sua aplicação como um pojo de pessoa.
getEntry: Retorna uma entrada do cache a partir de uma chave. Esta chave deve existir no servidor de cache do conrário você não tera o valor desejado, neste caso eu estou retornando o xml do Xstream que define o objeto, seria possível configurar o XStream para gerar JSon, a vantagem disso é que o EHCache server entende o content type de JSon :D
Algumas considerações sobre o código
A constante REST_CACHE_SERVER define onde esta o servidor de cache, perceba que o nome da aplicação web(.war) faz parte do url, caso você tenha empacotdo com outro nome, não esqueça de modificar este valor, bem como se você esta usando o as em outra porta.
Um teste básico
Agora podemos subir o JBoss AS com o servidor de cache dentro dele, vamos a um simples código de testes para verificar se tudo esta funcionando como deveria ser. Confira o código a baixo:
Ao rodar este código você deve ver a seguinte saida no console, confira a saida de console a baixo:
Se você quizer pode pegar os fontes completos e bem como o projeto do eclipse no meu repositório do subversion na web. Espero que você tenham gostado e que também tirem proveito do cache server em suas aplicações.
Abraços.
Muitas vezes o banco de dados acaba sendo um cargalo, uma vez que a sua rede esta 100% ok, uma forma viável de reduzir a carga no banco de dados é utilizar alguma forma de cache, se este cache tiver *hit*, ou seja, acertos em uma taxa considerável isso pode trazer muitas vantagens para a sua aplicação.
Cache com EHCache
EHCache é um solução open source de cache para Java. O EHCache lhe permite fazer vários tipos de cache como por exemplo aplicar cache junto ao Hibernate/JPA, cache de páginas web usando GZIP por exemplo e até mesmo cache distribuido que é o tema deste post.
EHCache - Arquitetura Modular
O EHCache tem uma arquitetura modular, logo você pode escolher os módulos que deseja utilizar. Neste post vou mostrar como trabalhar com o Cache Server.
REST EHCache Server
Quando precisamos trabalhar com cache distribuído existem 2 opções com o EHCache. A primeira é utilizar o cluster do próprio Terracota(solução de cache distribuído da Terracota, empresa que comprou o EHCache :-) ) a outra opção é usar o cache Server.
O Cache Server tem um API REST muito bacana. O mais legal disso é que como REST funciona em cima de http você pode consumir o cache server com qualquer linguagen/técnologia, por exemplo você poderia ter cache distribuido para várias aplicações escritas em linguágens diferentes e rodando em diverentes sistemas operacionais e distribuidos pelo mundo.
Outro ponto a favor do Cache Server ser em REST é que com isso podemos usar as soluções de loadbalance e até mesmo DNS, isso por que ele vai ficar rodando em um servidor de aplicação parão JEE. Por default o REST Server vem empacotado em uma distribuição standalone que roda com um Glassfish embutido, neste post vou mostrar como adaptar o REST EHCache Server para rodar no JBoss AS 5.
Ajustes para Rodar no JBoss AS 5.x
Primeiro você precisa baixar o ehcache server, depois disso vamos deletar algumas bibliotecas e adicionar outras, esta é a unica modificação que precisamos fazer para rodar ele no JBoss AS. Neste post vou utilizar a versão 1.0.0.
Entre na pasta onde você estraiu o cache server, vou me referir a esta pasta como $Download. Depois entre na seguinte pasta: $Download/war/WEB-INF/lib e delete os seguintes jars:
- activation-1.1.jar
- jaxb-api-2.0.jar
- jaxb-impl-2.1.9.jar
- stax-api-1.0.jar
- resolver-20050927.jar
Faça o deploy no JBoss AS 5.x e se divirta muito!
Manipulando o Servidor de Cache com REST
Agora vou mostrar como criar aréas de cache no REST Cache Server do EHCache e como adicionar e recuperar objetos deste cache, para isso vou utilizar dois frameworks que são o XStream e o HttpClient. Então vamos a classe genérica que criei para manipular o cache server chamei ele carinhosamente de HttpClientRESTBaby
<br>import java.io.BufferedReader;<br>import java.io.IOException;<br>import java.io.InputStream;<br>import java.io.InputStreamReader;<br>import java.io.OutputStream;<br>import java.io.OutputStreamWriter;<br>import java.io.Serializable;<br>import java.io.Writer;<br>import java.net.URI;<br><br>import org.apache.http.HttpEntity;<br>import org.apache.http.HttpResponse;<br>import org.apache.http.client.HttpClient;<br>import org.apache.http.client.methods.HttpGet;<br>import org.apache.http.client.methods.HttpPut;<br>import org.apache.http.client.methods.HttpRequestBase;<br>import org.apache.http.client.utils.URIUtils;<br>import org.apache.http.entity.BufferedHttpEntity;<br>import org.apache.http.entity.ContentProducer;<br>import org.apache.http.entity.EntityTemplate;<br>import org.apache.http.impl.client.DefaultHttpClient;<br><br>import com.thoughtworks.xstream.XStream;<br><br>/**<br> * <br> * @author Diego Pacheco<br> *<br> */<br>public class HttpClientRESTBaby {<br> <br> private static final String REST_CACHE_SERVER = "localhost:8080/ehcache-server-jboss-1.0.0/rest";<br> <br> private enum HTTP_METHOD { GET, PUT };<br> <br> private XStream xstream = new XStream();<br> <br> private boolean mute = false;<br> <br> <br> public void createCache(String cacheName) throws Throwable{<br> execute(HTTP_METHOD.PUT,cacheName, null);<br> }<br> <br> public String getCache(String cacheName) throws Throwable{<br> return execute(HTTP_METHOD.GET,cacheName, null);<br> }<br> <br> public void addEntry(String cacheName,String key,Serializable value)throws Throwable{ <br> execute(HTTP_METHOD.PUT,cacheName + "/" + key, value);<br> }<br> <br> public String getEntry(String cacheName,String key) throws Throwable{<br> return execute(HTTP_METHOD.GET,cacheName + "/" + key, null);<br> }<br> <br> private String execute(HTTP_METHOD method,String path,final Serializable value)throws Throwable{<br> <br> HttpClient httpclient = new DefaultHttpClient();<br> URI uri = URIUtils.createURI("http", REST_CACHE_SERVER, -1, path, null, null);<br> HttpRequestBase httpMethod = null;<br> <br> switch(method){<br> case PUT:<br> httpMethod = new HttpPut(uri); <br> if (value!=null){<br> ContentProducer cp = new ContentProducer() {<br> public void writeTo(OutputStream outstream) throws IOException {<br> Writer writer = new OutputStreamWriter(outstream, "UTF-8");<br> writer.write(xstream.toXML(value));<br> writer.flush();<br> }<br> };<br> HttpEntity entity = new EntityTemplate(cp);<br> ((HttpPut)httpMethod).setEntity(entity);<br> }<br> break;<br> case GET:<br> httpMethod = new HttpGet(uri); <br> break; <br> } <br> <br> if(mute==false)<br> System.out.println("++" + httpMethod.getURI()); <br> <br> HttpResponse response = httpclient.execute(httpMethod);<br> <br> BufferedHttpEntity entity = new BufferedHttpEntity(response.getEntity());<br> if (entity != null) {<br> if(mute==false){<br> System.out.println(" +Result["); <br> System.out.print(" ");<br> entity.writeTo(System.out);<br> System.out.println("\n +]");<br> }<br> } <br> <br> if(mute==false){<br> System.out.println(" +" + response.getStatusLine().getStatusCode());<br> System.out.println(" +" + response.getStatusLine().getReasonPhrase());<br> System.out.println(" +" + response.getStatusLine().toString() + "\n");<br> }<br> <br> return inputStreamToStringBuffer(entity.getContent()).toString(); <br> }<br> <br> private StringBuffer inputStreamToStringBuffer(InputStream is) throws Throwable{<br> <br> StringBuffer sb = new StringBuffer();<br> BufferedReader br = new BufferedReader(new InputStreamReader(is));<br> <br> String s = null;<br> while( (s = br.readLine()) != null ){ <br> sb.append(s);<br> }<br> return sb;<br> }<br><br> public void setMute(boolean mute) {<br> this.mute = mute;<br> } <br><br>}<br>
Esta classe eu estou disponibilizando os seguintes métodos:
- createCache(String cacheName)
- getCache(String cacheName)
- addEntry(String cacheName,String key,Serializable value)
- getEntry(String cacheName,String key)
getCache: Retorna um XML no padrão EHCache com a configuração de cache que esta associada a esta área de cache no server.
addEntry: Adiciona um elemento ao cache, perceba que é uma conbinação de chave/valor, onde a chave deve ser única, a chave vai acabar sendo parte do URL REST e o valor deve ser Serializable, logo você pode sim passar um Objeto do seu dominio da sua aplicação como um pojo de pessoa.
getEntry: Retorna uma entrada do cache a partir de uma chave. Esta chave deve existir no servidor de cache do conrário você não tera o valor desejado, neste caso eu estou retornando o xml do Xstream que define o objeto, seria possível configurar o XStream para gerar JSon, a vantagem disso é que o EHCache server entende o content type de JSon :D
Algumas considerações sobre o código
A constante REST_CACHE_SERVER define onde esta o servidor de cache, perceba que o nome da aplicação web(.war) faz parte do url, caso você tenha empacotdo com outro nome, não esqueça de modificar este valor, bem como se você esta usando o as em outra porta.
Um teste básico
Agora podemos subir o JBoss AS com o servidor de cache dentro dele, vamos a um simples código de testes para verificar se tudo esta funcionando como deveria ser. Confira o código a baixo:
<br>/**<br> * <br> * @author Diego Pacheco<br> *<br> */<br>public class MainRESTBabyRules {<br> <br> public static void main(String[] args) throws Throwable {<br> <br> HttpClientRESTBaby restBaby = new HttpClientRESTBaby();<br> restBaby.createCache("cacheName1");<br> restBaby.getCache("cacheName1");<br> <br> People p1 = new People();<br> p1.setId(1l);<br> p1.setName("Diego Pacheco");<br> <br> restBaby.addEntry("cacheName1", p1.getId().toString(), p1);<br> <br> String result = restBaby.getEntry("cacheName1", p1.getId().toString());<br> <br> People p2 = (People) new XStream().fromXML(result);<br> System.out.println("P2: " + p2.toString());<br> System.out.println("p1 is equals p2 ? " + p1.equals(p2) );<br> <br> }<br>}<br>
Ao rodar este código você deve ver a seguinte saida no console, confira a saida de console a baixo:
<br>++http://localhost:8080/ehcache-server-jboss-1.0.0/rest/cacheName1<br> +Result[<br> <br> +]<br> +201<br> +Created<br> +HTTP/1.1 201 Created<br><br>++http://localhost:8080/ehcache-server-jboss-1.0.0/rest/cacheName1<br> +Result[<br> <?xml version="1.0" encoding="UTF-8" standalone="yes"?><cache><cacheConfiguration><clearOnFlush>true</clearOnFlush><diskAccessStripes>1</diskAccessStripes><diskExpiryThreadIntervalSeconds>120</diskExpiryThreadIntervalSeconds><diskPersistent>false</diskPersistent><diskSpoolBufferSizeMB>30</diskSpoolBufferSizeMB><diskStorePath>C:\Users\DIEGOP~1\AppData\Local\Temp\</diskStorePath><eternal>false</eternal><loggingEnabled>false</loggingEnabled><maxElementsInMemory>10000</maxElementsInMemory><maxElementsOnDisk>10000000</maxElementsOnDisk><name>cacheName1</name><overflowToDisk>true</overflowToDisk><statistics>true</statistics><timeToIdleSeconds>120</timeToIdleSeconds><timeToLiveSeconds>120</timeToLiveSeconds></cacheConfiguration><description>[ name = cacheName1 status = STATUS_ALIVE eternal = false overflowToDisk = true maxElementsInMemory = 10000 maxElementsOnDisk = 10000000 memoryStoreEvictionPolicy = LRU timeToLiveSeconds = 120 timeToIdleSeconds = 120 diskPersistent = false diskExpiryThreadIntervalSeconds = 120 cacheEventListeners: net.sf.ehcache.statistics.LiveCacheStatisticsWrapper hitCount = 0 memoryStoreHitCount = 0 diskStoreHitCount = 0 missCountNotFound = 0 missCountExpired = 0 size = 0 ]</description><name>cacheName1</name><statistics><averageGetTime>0.0</averageGetTime><cacheHits>0</cacheHits><diskStoreSize>0</diskStoreSize><evictionCount>0</evictionCount><inMemoryHits>0</inMemoryHits><memoryStoreSize>0</memoryStoreSize><misses>0</misses><onDiskHits>0</onDiskHits><size>0</size><statisticsAccuracy>STATISTICS_ACCURACY_BEST_EFFORT</statisticsAccuracy></statistics><uri>http://localhost:8080/ehcache-server-jboss-1.0.0/rest/cacheName1</uri></cache><br> +]<br> +200<br> +OK<br> +HTTP/1.1 200 OK<br><br>++http://localhost:8080/ehcache-server-jboss-1.0.0/rest/cacheName1/1<br> +Result[<br> <br> +]<br> +201<br> +Created<br> +HTTP/1.1 201 Created<br><br>++http://localhost:8080/ehcache-server-jboss-1.0.0/rest/cacheName1/1<br> +Result[<br> <com.blogspot.diegopacheco.cache.playground.cacheserver.rest.domain.People><br> <id>1</id><br> <name>Diego Pacheco</name><br></com.blogspot.diegopacheco.cache.playground.cacheserver.rest.domain.People><br> +]<br> +200<br> +OK<br> +HTTP/1.1 200 OK<br><br>P2: ID: 1, Name: Diego Pacheco<br>p1 is equals p2 ? true<br>
Se você quizer pode pegar os fontes completos e bem como o projeto do eclipse no meu repositório do subversion na web. Espero que você tenham gostado e que também tirem proveito do cache server em suas aplicações.
Abraços.