Desenvolvimento em Java 2.0: Redis para o mundo real

Por que o Redis é melhor que o memcached em aplicativos muito lidos

O Redis tem muita coisa em comum com o memcached, mas dispõe de um conjunto maior de recursos. No Desenvolvimento em Java 2.0 deste mês, Andrew experimenta a inclusão do Redis (por meio da variante Jedis, baseada em Java™) em seu aplicativo remoto baseado em localização. Saiba como o Redis funciona como um armazenamento de dados simples, e depois tente alterar seu objetivo para um armazenamento em cache ultrarrápido e leve.

Andrew Glover, Author and developer, Beacon50

 Andrew GloverAndrew Glover é desenvolvedor, autor, palestrante e empresário com uma paixão por desenvolvimento orientado a comportamento, Integração Contínua e desenvolvimento de software Agile. É possível entrar em contato com ele em seu blog.



28/Dez/2011

Sobre esta série

O panorama de desenvolvimento em Java mudou radicalmente desde que a tecnologia Java emergiu. Graças a estruturas maduras de software livre e infraestruturas de implementação confiáveis de aluguel, agora é possível montar, testar, executar e manter aplicativos Java de forma rápida e barata. Nesta série, Andrew Glover explora o espectro de tecnologias e ferramentas que tornam este novo paradigma de desenvolvimento Java possível.

Já discuti o conceito de NoSQL antes nesta série e apresentei uma variedade de armazena mento de dados NoSQL compatíveis com a plataforma Java, incluindo Bigtable do Google e SimpleDB da Amazon. Também analisei armazenamentos de dados mais convencionais, baseados em servidores, como MongoDB e CouchDB. Cada armazenamento de dados tem pontos fortes e fracos, em especial quando aplicados a determinado cenário de domínio.

O Desenvolvimento em Java 2.0 deste mês se concentra no Redis, um armazenamento de dados leve de valor da chave. A maioria das implementações do NoSQL é essencialmente de valor da chave, mas o Redis suporta um conjunto extraordinariamente grande de valores, incluindo cadeias de caractere, listas, conjuntos e hashes. Assim, o Redis muitas vezes é chamado de servidor de estrutura de dados. O Redis também tem a reputação de ser extremamente rápido, o que faz dele a escolha ideal para determinada classe de casos de uso.

Ao tentar entender algo novo, pode ser útil compará-lo com algo que já conhecido, por isso, começaremos nossa exploração do Redis analisando sua similaridade com o memcached. Demonstrarei então os recursos-chave do Redis que talvez lhe deem uma vantagem sobre o memcached em alguns cenários de aplicativos. Por fim, mostrarei como usar o Redis como um armazenamento de dados tradicional para objetos modelo.

Redis e memcached

O memcached é um conhecido sistema de armazenamento em cache de objeto na memória que funciona colocando uma chave e valor-alvo em um cache de memória. O memcached evita assim o custo de E/S que acontece quando uma leitura ocorre no disco. Colocar o memcached entre um aplicativo da web e um banco de dados pode fornecer melhor desempenho de leitura. O memcached é, portanto, uma boa opção para aplicativos que exigem consultas rápidas de dados. Um exemplo seria serviços de consulta de ações que, de outra forma, consultaria um banco de dados com dados relativamente estáticos, como contador com nome ou até mesmo informações sobre preços.

MemcacheDB

Comparar o Redis com o memcached não é exatamente justo; é muito melhor empilhá-lo ao lado do MemcacheDB, que é um sistema de armazenamento de valor da chave distribuído projetado para persistência de dados. O MemcacheDB é bastante semelhante ao Redis e tem a vantagem de se comunicar facilmente com implementações de clientes do memcached.

Mas o memcached tem algumas limitações, incluindo o fato de que todos os seus valores são cadeias de caractere simples. O Redis, como alternativa ao memcached, suporta um conjunto mais variado de recursos. Algumas referências também indicam que o Redis é muito mais rápido do que o memcached. Os variados tipos de dados do Redis dados permitem armazenar dados muito mais sofisticados na memória do que se pode fazer com o memcached. E, ao contrário do memcached, o Redis consegue persistir seus dados.

O Redis é uma ótima solução de armazenamento em cache, mas seu conjunto variado de recursos leva a outros usos. Visto que o Redis é capaz de armazenar dados em disco e replicar os dados entre os nós, ele pode ser aproveitado como repositório de dados para modelos de dados tradicionais (ou seja, pode-se usar o Redis mais ou menos como se fosse RDBMS). O Redis também é usado muitas vezes como sistema de enfileiramento. Nesse caso de uso, o Redis é a base de um armazenamento de backup persistente de filas de trabalhos que aproveita o tipo de lista do Redis. O GitHub é um exemplo de infraestrutura em grande escala que usa o Redis dessa forma.


Obtenha o Redis e pronto!

Para começar com o Redis, obtenha acesso a ele, o que pode ser feito por meio de uma instalação local ou de um provedor hospedado. No caso de Mac, o processo de instalação não tem como ser mais fácil. Se estiver usando Windows®, será preciso ter o Cygwin instalado. Se estiver analisando provedores hospedados, o Redis4You tem um plano gratuito. Seja qual for seu modo de acessar o Redis, será possível acompanhar exemplos mais adiante neste artigo. Gostaria de salientar, porém, que o uso de um provedor de Redis hospedado para o armazenamento em cache talvez não seja uma ótima solução de armazenamento em cache, porque a latência da rede poderia anular qualquer ganho de desempenho.

Para interagir com o Redis, usamos comandos, ou seja, não há linguagem de consulta semelhante a SQL. Trabalhar com o Redis é muito parecido com trabalhar com uma estrutura de dados map tradicional — tudo tem uma chave e um valor, e cada valor tem um conjunto variado de tipos de dados associados a ele. Cada tipo de dados também tem seu próprio conjunto de comandos. Por exemplo, se planejamos usar tipos de dados simples, digamos, em alguma espécie de esquema de armazenamento em cache, podemos usar os comandos set e get.

Podemos interagir com uma instância do Redis por meio do shell da linha de comando. Também há diversas implementações cliente para trabalhar programaticamente com o Redis. A Listagem 1 mostra uma interação simples de shell de linha de comando usando comandos básicos:

Lista 1. Usando comandos básicos do Redis
redis 127.0.0.1:6379> set page registration
OK
redis 127.0.0.1:6379> keys *
1) "foo"
2) "page"
redis 127.0.0.1:6379> get page
"registration"

Aqui, associei a chave "page" ao valor "registration" por meio do comando set . A seguir, executei o comando keys (o * depois significa que quero ver todas as chaves de instância disponíveis). O comando keys mostra que há uma chave page , bem como uma foo . — Posso recuperar o valor associado à chave por meio do comando get . Lembre-se de que o valor recuperado de um get só pode ser uma cadeia de caractere. Se o valor de uma chave é uma lista, por exemplo, é preciso usar um comando de específico de lista para recuperar os elementos da lista. (Note que existem comandos para consultar o tipo de um valor.)


Integração de Java com Jedis

Para programadores que querem integrar o Redis em aplicativos Java, a equipe do Redis recomenda um projeto chamado Jedis. O Jedis é uma biblioteca leve que mapeia comandos nativos do Redis com métodos Java simples. Por exemplo, o Jedis permite obter e configurar valores simples como na Listagem 2:

Lista 2. Comandos básicos do Redis em código Java
JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");
Jedis jedis = pool.getResource();

jedis.set("foo", "bar");
String foobar = jedis.get("foo");
assert foobar.equals("bar");

pool.returnResource(jedis);
pool.destroy();

Na Listagem 2, eu configuro um conjunto de conexões e pego uma conexão (mais ou menos como se faz em um cenário JDBC típico), que então devolvo à parte inferior da listagem. Entre a lógica do conjunto de conexões, configuro o valor "bar" com a chave "foo", que recupero por meio do comando get .

Similar ao memcached, o Redis permite associar um prazo de expiração a um valor. Assim, posso configurar um valor (digamos, o preço de comercialização temporário de uma ação) que com o tempo será extraído do cache do Redis. Se quiser configurar um prazo de expiração no Jedis, faço isso após executar minha chamada set , associando-a ao prazo de expiração, como mostrado na Listagem 3:

Lista 3. Um valor de Redis pode ser configurado para expirar
jedis.set("gone", "daddy, gone");
jedis.expire("gone", 10);
String there = jedis.get("gone");
assert there.equals("daddy, gone");

Thread.sleep(4500);

String notThere = jedis.get("gone");
assert notThere == null;

Na Listagem 3, usei uma chamada expire para configurar o valor de "gone" para expirar em 10 segundos. Depois que Thread.sleep é chamado, um get por "gone" retornará null.

Tipos de dados em Redis

Trabalhar com os tipos de dados do Redis, como listas e hashes, requer uso especializado de comandos. Por exemplo, posso criar listas anexando valores a uma chave. No código da Listagem 4, executo o comando rpush , que anexa um valor à direita ou no fim da lista. (Um comando lpush corresponde coloca o valor na frente da lista.)

Lista 4. Listas do Redis
jedis.rpush("people", "Mary");
assert jedis.lindex("people", 0).equals("Mary");

jedis.rpush("people", "Mark");

assert jedis.llen("people") == 2;
assert jedis.lindex("people", 1).equals("Mark");

O Redis suporta uma ampla variedade de comandos que permitem trabalhar com tipos de dados; além disso, cada tipo de dados tem seu próprio conjunto de comandos. Em vez de repassá-los individualmente, vou mostrar alguns deles em operação em um cenário realista de desenvolvimento de aplicativos.


O Redis como solução de armazenamento em cache

Já mencionei que o Redis é facilmente usado como solução de armazenamento em cache, e acontece que preciso de uma! Neste exemplo de aplicativo, integraremos o Redis ao meu serviço da web móvel baseado em localização, chamado de Magnus.

Se não tem acompanhado esta série, eu implementei o Magnus pela primeira vez usando a estrutura Play, e já o desenvolvi ou reformulei em várias implementações desde então. O Magnus é um serviço simples que leva documentos JSON via solicitações PUT HTTP. Esses documentos descrevem a localização de determinada conta, o que significa uma pessoa com um dispositivo móvel.

Agora quero integrar armazenamento em cache ao Magnus — ou seja, quero reduzir o tráfego de E/S na forma de consulta por armazenamento, na memória, de dados que não mudam com frequência.

Caches do Magnus!

O primeiro testamento na Listagem 5 é descobrir se um nome da conta recebido (que é a chave) se encontra no Redis por meio de uma chamada get . Uma chamada para get retornará o ID da conta como valor ou retornará null. Se for retornado um valor, ele será usado como variável acctId . Se for retornado null , (indicando que o nome da conta não está no Redis como chave) consultaremos o valor da conta no MongoDB e o incluiremos no Redis por meio de um comando set .

A vantagem disso é a velocidade: da próxima vez que uma conta solicitada enviar uma localização, conseguiremos obter seu ID do Redis (agindo como cache de memória) em vez de ter de acessar o MongoDB e incorrer em um custo de E/S de leitura.

Lista 5. Usando o Redis como cache de memória
"/location/:account" {
  put {
    def jacksonMapper = new ObjectMapper()
    def json = jacksonMapper.readValue(request.contentText, Map.class)
    def formatter = new SimpleDateFormat("dd-MM-yyyy HH:mm")
    def dt = formatter.parse(json['timestamp'])
    def res = [:]
    
    try{

      def jedis = pool.getResource()	
      def acctId = jedis.get(request.parameters['account'])

      if(!acctId){
        def acct = Account.findByName(request.parameters['account'])
        jedis.set(request.parameters['account'], acct.id.toString())
        acctId = acct.id
      }

      pool.returnResource(jedis)
      new Location(acctId.toString(), dt, json['latitude'].doubleValue(), 
      json['longitude'].doubleValue() ).save()
      res['status'] = 'success'
    }catch(exp){
      res['status'] = "error ${exp.message}"
    }
   response.json = jacksonMapper.writeValueAsString(res)
  }
}

Note que a implementação aMagnus (escrita em Groovy) na Listagem 5 ainda usa uma implementação NoSQL para armazenamento de modelo de dados; usa o Redis apenas como implementação de cache para dados de consulta. Visto que os meus dados da conta primária residem no MongoDB (na verdade, residem em MongoHQ.com) e meu armazenamento de dados do Redis é executado localmente, o Magnus terá um aumento de significativo velocidade quando consultar IDs de conta subsequentes.

Mas espere! Por que preciso do MongoDB e do Redis? Não posso usar só um?

Node.js para ORM

Vários projetos fornecem um mapeamento semelhante ao ORM para o Redis, incluindo uma alternativa altamente influente e baseada em Ruby chamada Ohm. Verifiquei um derivado baseado em Java desse projeto (chamado JOhm), mas acabei decidindo usar uma variação escrita para Node. O bom de Ohm e seus projetos derivados é que eles permitem mapear um modelo de objeto em uma estrutura de dados baseada em Redis. Assim, seus objetos modelo são persistentes e (na maioria dos casos) extremamente rápidos em situações de leitura.

Usando Nohm, foi possível reescrever rapidamente o aplicativo Magnus em JavaScript e persistir os objetos Location em um piscar de olhos. Na Listagem 6, é definido um modelo Location q que inclui três propriedades. (Note que mantive o exemplo simples tornando timestamp uma cadeia de caractere em vez de um registro de data e hora verdadeiro.)

Lista 6. Redis ORM em Node.js
var Location = nohm.model('Location', {
	properties: {
	    latitude: {
	      type: 'float',
	      unique: false,
	      validations: [
	        ['notEmpty']
	      ]
	    },
		longitude: {
	      type: 'float',
	      unique: false,
	      validations: [
	        ['notEmpty']
	      ]
	    },
		timestamp: {
	      type: 'string',
	      unique: false,
	      validations: [
	        ['notEmpty']
	      ]
        }
     }
});

A estrutura Express do Node torna o uso do novo objeto Location de Nohm realmente fácil. Na implementação PUT do aplicativo, os valores de JSON recebidos são colocados em uma instância de Location por meio da chamada p de Nohm. Depois, verificamos se a instância é válida. Se for, pode ser persistida.

Lista 7. Usando Nohm em Express.js de Node
app.put('/', function(req, res) {
  res.contentType('json');
	
  var location = new Location;
  location.p("timestamp", req.body.timestamp);
  location.p("latitude", req.body.latitude);
  location.p("longitude", req.body.longitude);

  if(location.valid()){	
  	location.save(function (err) {
	  	if (!err) {
		    res.send(JSON.stringify({ status: "success" }));
		  } else {		
		   res.send(JSON.stringify({ status: location.errors }));
		  }
	  });
  }else{
   res.send(JSON.stringify({ status: location.errors }));
  }
});

Como mostra a Listagem 7, o Redis se destaca facilmente como um armazenamento de dados na memória extremamente rápido. E em alguns casos, talvez seja até um cache melhor do que o memcached!


Em conclusão

O Redis é útil para uma ampla variedade de cenários de armazenamento de dados, e porque é capaz de persistir dados em disco (e porque suporta um amplo conjunto de dados). Às vezes, se mostra um concorrente à altura do memcached. Em casos onde faz sentido para seu domínio, pode-se usar o Redis como armazenamento de backup para modelos de dados e filas. Implementações de clientes do Redis foram transferidas para praticamente todas as linguagens de programação que existem.

O Redis não substitui totalmente o RDMBS, nem é um armazenamento pesado, cheio de recursos de consulta, como o MongoDB. Mas na maioria dos casos pode conviver com essas tecnologias. Como mostrado neste artigo, o Redis pode ser uma boa solução de armazenamento de dados independente para aplicativos que executam muitas consultas de dados ou quando estatísticas em tempo real poderem ser feitas por meio das operações atômicas rápidas do Redis.

Recursos

Aprender

Obter produtos e tecnologias

  • Faça o download de Redis e Jedis: Redis é um servidor de armazenamento de valor da chave e de estrutura de dados em software livre; Jedis é o cliente recomendado atual para desenvolvimento baseado em Java.
  • Get Nohm: uma implementação Node.js do mapeador relacional a objeto do Redis, o Ohm.

Discutir

  • Participe da comunidade do My developerWorks. Entre em contato com outros usuários do developerWorks e explore os blogs, fóruns, grupos e wikis voltados para desenvolvedores.

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java, Cloud computing
ArticleID=783110
ArticleTitle=Desenvolvimento em Java 2.0: Redis para o mundo real
publish-date=12282011