O Redis é um banco de dados NoSQL escrito em C. O Remote Dictionary Server é um banco de dados chave-valor cujo o armazenamento é feito na memória, fazendo com que a escrita e a leitura sejam rápidos.
Mas qual a diferença entre ele o cache? O que acontece quando o banco cai? Haverá perda total dos dados? O objetivo desse artigo é falar um pouco desse banco de dados e demonstrar o seu uso prático utilizando um pequeno projeto open source, o redis-collections.
Considerando que as ferramentas de caches, como memcache e infinitspam, também possui esse comportamento de chave-valor, a primeira dúvida normal de um desenvolvedor é saber qual a diferença entre os dois. A primeira diferença está na serialização dos objetos valores: enquanto os caches escritos em Java são serializados de forma binária, com o Kryo ou o java.io.Serializable, por exemplo, para o redis tudo é texto. Um problema da serialização binária é a impossibilidade de realizar alterações de forma manual como em um texto. Ao usar o Kryo ou o java.io.Serializable, caso haja uma alteração significativa dentro da estrutura do objeto, não será mais possível deserializar tal estrutura no novo objeto, ou seja, ao retornar para a estrutura anterior ou descartar as informações. A razão disso é que os binários tendem a ser mais rápido para leitura e escrita e mais compactos, e os caches não são feitos para perdurar por longo período de tempo, A grande maioria dura enquanto o programa está rodando.
Outra diferença está no tipo de valores: enquanto o cache trata apenas chave e valor, sendo que o valor é um objeto, no redis existem diferentes estruturas de dados no lado do valor. São eles:
- String chave e String valor;
- List: uma coleção de String ordenado de acordo com a ordem de inserção, semelhante a coleção java.util.LinkedList;
- Set: coleção única de string não ordenadas, semelhante ao java.util.Set;
- Sorted Set: similar ao set, por não permitir valores duplicados, mas cada elemento está associado para um valor flutuante, chamado de score. Esses elementos são sempre ordenados por esse campo score;
- Hashser: Composto de campos e seus respectivos valores (ambos são String), semelhante ao Map.
Além desses que são os mais conhecidos, existem também o Bit arrays e HyperLog que não serão abordados nesse artigo.
Uma vez explicando as diferenças básicas entre o Redis e um cache, o próximo passo será a instalação do banco que é bastante simples:
- Realizar o Download aqui;
- Descompartar, abrir no terminal do redis e em seguida compilar o redis, conforme o código abaixo:
tar zxvf redis-versao.x.tar.gz cd redis-versao.x make
- Uma vez compilado, entrar na pasta src e executar o servidor, conforme o código abaixo:
cd src ./redis-server
Pronto, o Redis está rodando. Para realizar alguns testes, basta executar o cliente que já vem com o ele. Para isso, basta ir em REDIS_HOME/src e em seguida executar o comando ./redis-cli.
Para saber mais sobre os comandos, acesse o Site da redis
Uma vez instalado e testado, o nosso objetivo agora será a utilização de algumas estruturas de dados em cima do redis. Nosso objetivo será a utilização e a implementação de java.util.List, java.util.Set, java.util.Queue e java.util.Map, além de mais três estruturas: o conceito de chave e valor (semelhante ao cache), uma para contador e outra para fazer Ranking, sorted Set.
Ao idealizar a API, vamos ter o mesmo código da Listagem 1.
public interface keyValueRedisStructure<T> {
T get(String key);
void set(String key, T bean);
List<T> multiplesGet(Iterable<String> keys);
void delete(String key);
}
public interface CountStructure<T extends Number> {
T get();
T increment();
T increment(T count);
T decrement();
T decrement(T count);
void delete();
void expires(int ttlSeconds);
void persist();
}
public interface ListStructure <T> extends Expirable {
List<T> get(String key);
void delete(String key);
}
public interface MapStructure <T> extends Expirable{
Map<String, T> get(String key);
void delete(String key);
}
public interface QueueStructure <T> extends Expirable {
Queue<T> get(String key);
void delete(String key);
}
public interface RankingStructure<T extends Number> extends Expirable {
ScoresPoint<T> create(String key);
void delete(String key);
}
Uma vez vendo a estrutura, uma das dúvidas é o Expirable, devido a ideia em cima da chave. Conseguimos definir o tempo de vida em segundos do respectivo valor e para anular esse tempo de expiração existe o comando persist. Vale lembrar que mesmo sendo em memória, o redis de tempos em tempos realiza backup das informações e, dessa forma, caso o banco caia as informações serão mantidas. Como dito anteriormente, o redis trata como a chave e os valores como String. Dessa forma, para seralizar e desearlizar o objetivo precisar ser em texto. Por ser mais “leve”, a estrutura escolhida será o JSON utilizando o framework de leitura e escrita de JSON do Google - o GSON, mas poderia ser qualquer outra forma, por exemplo, o XML tem campos separados por pipe “|” etc. E para se comunicar com o redis será utilizado o Jedis.
Porque java.util?
As coleções dentro do java.util certamente são conhecidas pela grande maioria dos desenvolvedores Java. Dessa forma, não existiria dificuldade destes para a utilizarem.
A grande diferença entre as implementações dentro do JDK e a dessas coleções é que as últimas serão implementadas no modo lazy. Enquanto as implementações de List, como ArrayList e LinkedList, já possuem as informações, por exemplo, o RedisList usará o Jedis, a API de comunicação com o redis para realizar uma operação (seja inserir, verificar o tamanho ou retornar os valores dentro do redis). Desse é dito que um RedisList é igual ao outro se ele tiver a mesma chave.
Convenção do namespace
Por convenção, o redis utiliza o conceito de namespace que funciona como um prefixo da chave. O seu formato é: namespace:chave.
Por exemplo, para registrar informações do usuário na seção seria users:nickname.
Cliente do Redis
O Jedis é o client para o redis feito para o Java e com ele é possível realizar todas as operações em cima das chaves. O seu uso é bastante simples, conforme mostra o código abaixo:
Jedis jedis = new Jedis("localhost");
jedis.set("foo", "bar");
String value = jedis.get("foo");
No exemplo acima foi criada uma conexão para o redis na nossa máquina localhost e, em seguida, foram feitas duas operações simples: Setar o bar dentro da chave foo e retornar o valor dentro da chave “foo”.
O Redis possui uma arquitetura distribuída, ou seja, pode-se trabalhar com clusters. Para fazer o client com nós dentro do Jedis, é necessário utilizar o comando da Listagem 3.
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
//O jedis cluster irá descobrir os outros nós automaticamente.
jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7379));
JedisCluster jc = new JedisCluster(jedisClusterNodes);
jc.set("foo", "bar");
String value = jc.get("foo");
Criando as estruturas
A base para a criação das estruturas é o RedisStrutureBuilder, sendo que, entre os métodos, o único campo obrigatório é o Jedis, que como dito anteriormente, é a nossa comunicação entre a nossa aplicação e o Redis. Acompanhe o código da Listagem 4.
ListStructure<ProductCart> shippingCart = RedisStrutureBuilder.ofList(jedis,
ProductCart.class).withNameSpace("list_producs").build();
List<ProductCart> fruitsCarts = shippingCart.get(FRUITS);
fruitsCarts.add(banana);
ProductCart banana = fruitsCarts.get(0);
User otaviojava = new User("otaviojava");
User felipe = new User("ffrancesquini");
SetStructure<User> socialMediaUsers = RedisStrutureBuilder.ofSet(RedisConnection.JEDIS,
User.class).withNameSpace("socialMedia").build();
Set<User> users = socialMediaUsers.createSet("twitter");
users.add(otaviojava);
users.add(otaviojava);
users.add(felipe);
users.add(otaviojava);
users.add(felipe);
//haverá apenas um objeto otaviojava e um felipe, já que ele impede a
duplicação da lista
//lembrando que os métodos de equals e hascode dos objetos não serão
utilizados, o que será considerado é a String do objeto gerado.
Species mammals = new Species("lion", "cow", "dog");
Species fishes = new Species("redfish", "glassfish");
Species amphibians = new Species("crododile", "frog");
MapStructure<Species> zoo = =
RedisStrutureBuilder.ofMap(RedisConnection.JEDIS, Species.class)
.withNameSpace("animalZoo").build();
Map<String, Species> vertebrates = zoo.get("vertebrates");
vertebrates.put("mammals", mammals);
vertebrates.put("mammals", mammals);
vertebrates.put("fishes", fishes);
vertebrates.put("amphibians", amphibians);
QueueStructure<LineBank> serviceBank =
RedisStrutureBuilder.ofQueue(RedisConnection.JEDIS,
LineBank.class).withNameSpace("serviceBank").build();
QueueStructure<LineBank> serviceBank =
RedisStrutureBuilder.ofQueue(RedisConnection.JEDIS,
LineBank.class).withNameSpace("serviceBank").build();
Queue<LineBank> lineBank = serviceBank.get("createAccount");
lineBank.add(new LineBank("Otavio", 25));
LineBank otavio = lineBank.poll();
Com isso, foi discutido nesse artigo um pouco mais sobre o redis e um exemplo prático utilizando implementações das coleções Java para o redis. Salientamos a diferença entre o redis o cache. Os caches apresentados acima, por usarem formato binário e usarem o Kryo ou o java.io.Serializable acabam sendo mais compactos e rápidos na leitura e escrita. No entanto, uma grande mudança no objeto acaba impossibilitando a recuperação das informações antes da mudança, sendo imprescindível a eliminação das informações anteriores, afinal. É esse o objetivo do cache: um bloco de memória de acesso rápido de armazenamento temporário. Já o redis é um banco de dados chave valor cujo o armazenamento fica na memória. No entanto, de tempos em tempos é feito um autobackup das informações no disco com o intuito de, caso aconteça uma queda, não haja perdas das informações. E o outro fator é que o redis está no seu armazenamento utilizando String tanto na chave como no valor e possui algumas estruturas na parte do valor.