Memcached é um sistema de cache em memória distribuido muito fácil de usar. Como recentemente tive uma experiência maravilhosa com esta ferramenta acredito que é interessante expô-la neste post (prometo que o próximo post será sobre MongoDB). A natureza do Memcached é extremamente genérica – trata-se de um serviço de rede – sendo assim, o que descreverei pode ser aplicado sem grandes mudanças a básicamente qualquer linguagem/ambiente de execução. Mas antes de falar sobre minha experiência, vou falar um pouco sobre o funcionamento da criatura e como usá-lo com Java (consequentemente, com Groovy também).
Idéia básica
Para muitos, o conceito de cache é algo novo. Sendo assim, convém fazer uma introdução ao conceito. Imagine que você possua um conjunto de informações que repetidas vezes precise ser buscado na sua base de dados. A cada consulta, você precisa:
- Enviar a consulta (por exemplo: SQL)
- Esperar o processamento do SGBD
- Receber o resultado obtido
- Transformá-lo, tornando-o útil para sua aplicação
O objetivo do cache é evitar temporáriamente os três últimos passos do procedimento que descrevi acima, retornando para o usuário o resultado do processamento dos dados em estado cru. Se 2 + 2 são 4, por que calcular este valor toda vez que você receber esta pergunta se é possível armazenar apenas o resultado e reaproveitá-lo quando necessário?
O Servidor Memcached
O Memcached é um serviço de rede. No site oficial (http://www.memcached.org) é possível baixar a última versão, que vêm como código fonte (veja este link para maiores detalhes sobre como proceder com a instalação). Se estiver usando Linux, instalá-lo é ainda mais fácil, visto que muito provávelmente a criatura já estará disponível nos repositórios de sua distribuição.
Instalado, basta executar o comando memcached para iniciar o serviço. É importante conhecer alguns parâmetros, cuja listagem segue abaixo:
-m : quanta memória (em Mbs) o Memcached irá usar. Caso omitido, o valor default é 64 Mb.
-p : qual a porta TCP a ser usada. Caso omitido, o valor default é 11211
-u : qual a porta UDP a ser usada. Como a porta TCP, o valor default é 11211 caso omitido.
-v : Modo verboso: o Memcached irá expor na tela o que está executando. É bacana quando estamos aprendendo a usar o bichinho. Interessante mencionar que há duas outras opções: -vv (mais verboso) e -vvv (ainda mais verboso)
Sendo assim, se eu executar o comando memcached -m 540 -v -p 11000 -u 11000 iniciará o serviço com 540 Mb de memória no modo verboso definindo como portas UDP e TCP a 11000.
Como mencionei, o Memcached é um servidor distribuido, mas como minha experiência até o momento envolveu apenas uma instância ainda não tenho conhecimento suficiente para compartilhar.
O que é e como é armazenado
Pense no Memcached como uma tabela de hash gigante. Toda informação individual armazenada possui basicamente 3 elementos:
Identificador: um texto com tamanho máximo de 250 caracteres que identifica um corpo de informação. Equivale à chave primária com a qual já estamos acostumados a trabalhar no modelo relacional. Não há regras com relação ao seu valor: sendo assim, você é livre para identificar suas informações como quiser.
Tempo de duração: o tempo (em segundos) que o Memcached manterá esta informação na memória. O tempo máximo suportado é 30 dias.
Corpo: a informação em si. No caso do Memcached, esta possui tamanho máximo de 1 Mb. Se for pouco pra você, sempre é possível dividir a sua informação em mais de um bloco, compactá-la ou, se preferir, alterar o código fonte do Memcached (eu o li inteiro, e é BEM fácil de entender (se você souber C)).
Lembre-se: não se trata de um banco de dados. Tudo no Memcached, assim como na vida (sôou poético!) é passageiro. A idéia é evitar que tenhamos de acessar o SGBD, que é um processo MUITO mais caro computacionalmente, e não substitui-lo!
O cliente Memcached
O primeiro passo é escolher qual biblioteca cliente usar para acessar o Memcached. Há diversas, implementadas em tudo o que você imaginar: C, C++, PHP, MySQL, Python, Perl, Ruby… No nosso caso, vou falar do SpyMemcached, que é um cliente para Java. Uma lista com “alguns” dos clientes disponíveis atualmente pode ser acessada neste link.
O SpyMemcached é composto por um único arquivo Jar, o que torna seu deploy muito simples. Basicamente o único pacote que você vai precisar é do net.spy.memcached.
Obtendo uma conexão com o Memcached
Basta criar uma nova instância da classe MemcachedClient, tal como no exemplo abaixo:
MemcachedClient client =new MemcachedClient(AddrUtil.getAddresses("0.0.0.0:11211 10.10.10.69:11211"));
Repare que passei dois servidores distintos. Usando este construtor você poderá adicionar um ou mais servidores aonde serão feitas as suas buscas.
Buscando e inserindo informações
Buscar e inserir informações com o SpyMemcached é simples. O código abaixo é quase que auto-explicativo.
MemcachedClient client =new MemcachedClient(AddrUtil.getAddresses("localhost:11211")); // O que irei armazenar no Memcached Pessoa pessoa = new Pessoa(); pessoa.setNome("Kico"); pessoa.setCidade("BH"); // Incluindo informações client.add("chave_pessoa", 120, pessoa); // Buscando a informação Pessoa noCache = (Pessoa) client.get("chave_pessoa");
Quando incluimos uma informação no Memcached, devemos passar 3 parâmetros: a chave de identificação, o tempo em segundos e o que queremos armazenar. No caso de um objeto, este obrigatóriamente deverá implementar a interface java.io.Serializable. Caso contrário será disparada uma excessão.
Já para obter a informação, basta passar a chave que a identifica e em seguida fazer o type casting para o tipo desejado.
Ah: e como fechar a conexão com o servidor? Simples: execute o método shutdown() da classe MemcachedClient.
Conhecendo os métodos abaixo da classe MemcachedClient você já pode começar a trabalhar (e bem) com o Memcached:
void MemcachedClient.add(String chave, int segundos, Object valor): insere uma informação no servidor Memcached. Se já existir um valor definido para esta chave no servidor, este será mantido pelo Memcached.
Object MemcachedClient.get(String chave): retorna um valor armazenado no Memcached
void MemcachedClient.shutdown(): fecha a conexão com o Memcached
void MemcachedClient.remove(String chave): remove uma informação armazenada no servidor
void MemcachedClient.replace(String chave, int segundos, Object valor): substitui um valor armazenado no servidor.
Dica: aproveite ao máximo suas conexões com o Memcached buscando o maior número possível de registros, mas nunca se esqueça de fechá-las, pois o serviço começa a apresentar problemas quando o número de conexões simultâneas é muito alto.
Minha experiência
Eis a situação: possuimos uma aplicação feita em Grails cuja base é uma biblioteca escrita em Java. Esta biblioteca é também usada por diversas aplicações executadas no ambiente desktop dentro da empresa. Fiz uma imagem que, espero, ilustre a situação.
Em nosso servidor físico aonde já se encontrava instalado o Tomcat aproveitei para colocar em execução o servidor do Memcached.
Como sou o pai da biblioteca legada que mencionei acima, a refatorei para que ao invés de usar a biblioteca de cache anterior, passasse a usar o Memcached.
Em seguida, atualizei tanto a nossa aplicação Grails quanto os clientes desktop (nestas horas você começa a AMAR o Java Webstart) e voilá: resultado imediato.
Como todos acessam o mesmo servidor Memcached, no momento em que uma aplicação desktop ou web alimentam o servidor de cache, automáticamente todas as demais instâncias se beneficiam. Resultado? Nossa performance global aumentou no mínimo 3 vezes, e o número de chamadas ao nosso SGBD diminuiu em aproximadamente 40%.
É importante mencionar que em nosso ambiente é muito comum mais de um usuário concorrentemente necessitar do mesmo conjunto de informações. Isto é fundamental, pois caso contrário não teriamos um ganho de performance, mas sim perda, pois antes de executar uma consulta no SGBD, sempre seria feita uma busca no Memcached.
É fundamental lembrar o seguinte aqui: não temos um cache local, mas remoto. O ganho da performance é obtido em grande parte porque não precisamos popular objetos, visto os mesmos já virem “prontos” do servidor para nós.
Antes do Memcached haviamos pensado sériamente em usar o Terracota. Mas como nosso ambiente é heterogêneo, e há programas escritos em .net, C/C++, VB6 e PHP, o Memcached caiu como uma luva, pois assim podemos aproveitar a mesma estrutura (e dados!) entre estas diferentes plataformas de execução/desenvolvimento (claro, com os devidos cuidados para evitar a bagunça).
Recomendadíssimo portanto o seu uso. Espero que este post seja útil aos que estejam interessados em usar a ferramenta.
Olá Henrique,
Vi que você comentou sobre o MongoDB, você por um acaso já teve alguma experiência com o Memcache e algum banco de dados NoSQL como MongoDB, Cassandra, etc ….. Se não me engano o pessoal do Digg usou o cassandra junto o memcache e provavelmente tiveram resultados bons.
Abraços e muito bom o post
Oi Breno, fico feliz que tenha gostado.
Não, ainda não tive nenhuma experiência neste sentido, mas acredito que não venha a ser muito diferente desta que mencionei aqui no post.
De qualquer maneira, assim que tiver publico aqui. :)
Olá Kiko. Não consegui fazer a instalação no windows. O memcached é portável apenas para o linux? Tenhos servidores linux mais quero fazer testes locais antes.
Grato,
Oi Daniel, até agora só instalei o memcached no Linux e Mac OS compilando, porém econtrei uma versão já compilada pro Windows.
Experimenta esta: http://jehiah.cz/projects/memcached-win32/ e depois me diga o que achou ok?
Grande abraço!
Olá Kico.
Consegui resolver. Só que eu não usei esse port que vc me passou não.
Usei esse aqui que tem uma versão mais atual:
http://labs.northscale.com/memcached-packages/
Gostei bastante do memcached kiko. Muito massa. Parabéns pelo artigo tb, nem precisou ver a documentação heheh.
Abraço!
Valeu Daniel, precisando de qualquer coisa estou à sua disposição!
Estou fazendo testes mais no client memcached no eclipse no console gera um monte de erros e fica repetindo…..
2012-04-07 10:24:34.181 INFO net.spy.memcached.MemcachedConnection: Added {QA sa=/IP:11211, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
2012-04-07 10:24:34.199 WARN net.spy.memcached.MemcachedConnection: Could not redistribute to another node, retrying primary node for chave_pessoa121.
2012-04-07 10:24:34.203 WARN net.spy.memcached.MemcachedConnection: Could not redistribute to another node, retrying primary node for chave_pessoa121.
2012-04-07 10:24:35.218 INFO net.spy.memcached.MemcachedConnection: Connection state changed for sun.nio.ch.SelectionKeyImpl@10f98160
2012-04-07 10:24:35.219 INFO net.spy.memcached.MemcachedConnection: Reconnecting due to failure to connect to {QA sa=/IP:11211, #Rops=0, #Wops=2, #iq=0, topRop=null, topWop=Cmd: add Key: chave_pessoa121 Flags: 1 Exp: 3600 Data Length: 99, toWrite=0, interested=0}
java.net.ConnectException: Connection refused: no further information
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
at sun.nio.ch.SocketChannelImpl.finishConnect(Unknown Source)
at net.spy.memcached.MemcachedConnection.handleIO(MemcachedConnection.java:369)
at net.spy.memcached.MemcachedConnection.handleIO(MemcachedConnection.java:242)
at net.spy.memcached.MemcachedConnection.run(MemcachedConnection.java:833)
2012-04-07 10:24:35.222 WARN net.spy.memcached.MemcachedConnection: Closing, and reopening {QA sa=/IP:11211, #Rops=0, #Wops=2, #iq=0, topRop=null, topWop=Cmd: add Key: chave_pessoa121 Flags: 1 Exp: 3600 Data Length: 99, toWrite=0, interested=0}, attempt 1.
2012-04-07 10:24:39.225 INFO net.spy.memcached.MemcachedConnection: Reconnecting {QA sa=/IP:11211, #Rops=0, #Wops=2, #iq=0, topRop=null, topWop=Cmd: add Key: chave_pessoa121 Flags: 1 Exp: 3600 Data Length: 99, toWrite=0, interested=0}
2012-04-07 10:24:40.284 INFO net.spy.memcached.MemcachedConnection: Connection state changed for sun.nio.ch.SelectionKeyImpl@2b9e774d
2012-04-07 10:24:40.285 INFO net.spy.memcached.MemcachedConnection: Reconnecting due to failure to connect to {QA sa=/IP:11211, #Rops=0, #Wops=2, #iq=0, topRop=null, topWop=Cmd: add Key: chave_pessoa121 Flags: 1 Exp: 3600 Data Length: 99, toWrite=0, interested=0}
java.net.ConnectException: Connection refused: no further information
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
Olá, já tentei testar o Memcached em C# mas não tive sucesso então estou agora estudando a implementação com Java.
Consegui realizar o exemplo com sucesso, porém agora preciso realizar um teste mais real, armazenando uma consulta inteira do banco de dados em uma unica chave.
O que eu preciso é um método que verificar se a chave existe, se sim transforme a String em uma lista de objetos Pessoa (é possível?) e retorna essa lista para o chamador e se a chave não existir ai procura no banco (eu uso Hibernate, esta parte já funciona).
Como eu faço essa condição?
Obrigado.
Boa tarde amigo, estou passando por um problema com dois servidores de aplicação web com PHP e o memcache. Ambos os meus servidores estão sendo apontados para o memcache, apenas para armazenar a sessão dos usuários, e é justamente ai o problema. Depois que implantamos o memcache a aplicação não perde mais a sessão, então o timeout do usuário que antes era de duas horas, agora parece ser infinito! Nossa aplicação utiliza o CodeIgniter de framework, e não estamos conseguindo identificar o que está causando esse login permanente.. No arquivo php.ini alteramos o apenas dois parâmetros, que é como ele armazenará as sessões, e onde.. E mesmo assim o login permanente continua.. Alguma dica??
Obrigado!
Oi Felipe,
no caso do PHP não sei como se procede, mas até aonde sei tem como você colocar uma data de expiração no memcached. Em teoria isto resolveria seu problema.