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.


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
Agora gere um war com todo o conteudo das pastas WEB-INF e META-INF, pode ser usando as ferramentas do Java, pode ser com o seu IDE, eu fiz isso usando o winrar :D

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)
createCache: Cria uma área de cache dentro do cache server, você chama uma vez e ele cria a área, se você chamar de novo ele vai retornar um conflito por que o cache já foi criado no server.

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.

Popular posts from this blog

Telemetry and Microservices part2

Installing and Running ntop 2 on Amazon Linux OS

Fun with Apache Kafka