Introdução ao Jedis - a biblioteca cliente Java Redis
1. Visão geral
Este artigo éan introduction to Jedis, uma biblioteca cliente em Java paraRedis - o popular armazenamento de estrutura de dados na memória que também pode persistir no disco. Ele é direcionado por uma estrutura de dados baseada em keystore para manter os dados e pode ser usado como banco de dados, cache, intermediário de mensagens, etc.
Primeiro, vamos explicar em que tipo de situações Jedis é útil e sobre o que se trata.
Nas seções subseqüentes, estamos elaborando as várias estruturas de dados e explicando transações, pipelining e o recurso de publicação / assinatura. Concluímos com o pool de conexões e o Redis Cluster.
2. Por que Jedis?
O Redis lista as bibliotecas de cliente mais conhecidas em seusofficial site. Existem várias alternativas para Jedis, mas apenas mais duas são atualmente dignas de sua estrela de recomendação,lettuce eRedisson.
Esses dois clientes têm alguns recursos exclusivos, como segurança de encadeamento, manipulação de reconexão transparente e uma API assíncrona, recursos dos quais Jedis não possui.
No entanto, é pequeno e consideravelmente mais rápido que os outros dois. Além disso, é a biblioteca cliente preferida dos desenvolvedores do Spring Framework, e possui a maior comunidade dos três.
3. Dependências do Maven
Vamos começar declarando a única dependência de que precisaremos empom.xml:
redis.clients
jedis
2.8.1
Se você está procurando a versão mais recente da biblioteca, verifiquethis page.
4. Instalação do Redis
Você precisará instalar e iniciar uma das versões mais recentes do Redis. Estamos executando a versão estável mais recente no momento (3.2.1), mas qualquer versão posterior à 3.x deve estar correta.
Encontrehere mais informações sobre Redis para Linux e Macintosh; eles têm etapas básicas de instalação muito semelhantes. O Windows não é oficialmente suportado, mas esteport é bem mantido.
Depois disso, podemos nos aprofundar e conectar diretamente a ele a partir do nosso código Java:
Jedis jedis = new Jedis();
O construtor padrão funcionará perfeitamente, a menos que você tenha iniciado o serviço em uma porta não padrão ou em uma máquina remota; nesse caso, você pode configurá-lo corretamente, passando os valores corretos como parâmetros para o construtor.
5. Estruturas de dados Redis
A maioria dos comandos de operação nativos é suportada e, convenientemente, eles normalmente compartilham o mesmo nome de método.
5.1. Cordas
Strings são o tipo mais básico de valor Redis, útil para quando você precisa persistir tipos de dados simples de valor-chave:
jedis.set("events/city/rome", "32,15,223,828");
String cachedResponse = jedis.get("events/city/rome");
A variávelcachedResponse manterá o valor32,15,223,828. Juntamente com o suporte à expiração, discutido mais adiante, ele pode funcionar como uma camada de cache extremamente rápida e simples de usar para solicitações HTTP recebidas em seu aplicativo Web e outros requisitos de armazenamento em cache.
5.2. Listas
As listas Redis são simplesmente listas de strings, classificadas por ordem de inserção e a tornam uma ferramenta ideal para implementar, por exemplo, filas de mensagens:
jedis.lpush("queue#tasks", "firstTask");
jedis.lpush("queue#tasks", "secondTask");
String task = jedis.rpop("queue#tasks");
A variáveltask manterá o valorfirstTask. Lembre-se de que você pode serializar qualquer objeto e persistir como uma sequência, para que as mensagens na fila possam transportar dados mais complexos quando necessário.
5.3. Sets
Redis Sets são uma coleção não ordenada de Strings que são úteis quando você deseja excluir membros repetidos:
jedis.sadd("nicknames", "nickname#1");
jedis.sadd("nicknames", "nickname#2");
jedis.sadd("nicknames", "nickname#1");
Set nicknames = jedis.smembers("nicknames");
boolean exists = jedis.sismember("nicknames", "nickname#1");
O conjunto Javanicknames terá um tamanho de 2, a segunda adição denickname#1 foi ignorada. Além disso, a variávelexists terá um valor detrue, o métodosismember permite que você verifique a existência de um determinado membro rapidamente.
5.4. Hashes
Os hashes do Redis são mapeados entre os camposString e os valoresString:
jedis.hset("user#1", "name", "Peter");
jedis.hset("user#1", "job", "politician");
String name = jedis.hget("user#1", "name");
Map fields = jedis.hgetAll("user#1");
String job = fields.get("job");
Como você pode ver, os hashes são um tipo de dados muito conveniente quando você deseja acessar as propriedades do objeto individualmente, uma vez que você não precisa recuperar o objeto inteiro.
5.5. Conjuntos classificados
Conjuntos classificados são como um conjunto em que cada membro tem uma classificação associada, usada para classificá-los:
Map scores = new HashMap<>();
scores.put("PlayerOne", 3000.0);
scores.put("PlayerTwo", 1500.0);
scores.put("PlayerThree", 8200.0);
scores.entrySet().forEach(playerScore -> {
jedis.zadd(key, playerScore.getValue(), playerScore.getKey());
});
String player = jedis.zrevrange("ranking", 0, 1).iterator().next();
long rank = jedis.zrevrank("ranking", "PlayerOne");
A variávelplayer manterá o valorPlayerThree porque estamos recuperando o primeiro jogador e ele é aquele com a maior pontuação. A variávelrank terá um valor de 1 porquePlayerOne é o segundo na classificação e a classificação é baseada em zero.
6. Transações
As transações garantem operações de atomicidade e segurança de encadeamento, o que significa que solicitações de outros clientes nunca serão tratadas simultaneamente durante as transações Redis:
String friendsPrefix = "friends#";
String userOneId = "4352523";
String userTwoId = "5552321";
Transaction t = jedis.multi();
t.sadd(friendsPrefix + userOneId, userTwoId);
t.sadd(friendsPrefix + userTwoId, userOneId);
t.exec();
Você pode até mesmo fazer com que o sucesso de uma transação dependa de uma chave específica, "observando" antes de instanciarTransaction:
jedis.watch("friends#deleted#" + userOneId);
Se o valor dessa chave for alterado antes da transação ser executada, a transação não será concluída com êxito.
7. Pipelining
Quando precisamos enviar vários comandos, podemos agrupá-los em uma solicitação e economizar a sobrecarga da conexão usando pipelines, é essencialmente uma otimização de rede. Desde que as operações sejam independentes entre si, podemos tirar proveito desta técnica:
String userOneId = "4352523";
String userTwoId = "4849888";
Pipeline p = jedis.pipelined();
p.sadd("searched#" + userOneId, "paris");
p.zadd("ranking", 126, userOneId);
p.zadd("ranking", 325, userTwoId);
Response pipeExists = p.sismember("searched#" + userOneId, "paris");
Response> pipeRanking = p.zrange("ranking", 0, -1);
p.sync();
String exists = pipeExists.get();
Set ranking = pipeRanking.get();
Observe que não temos acesso direto às respostas do comando, em vez disso, recebemos uma instânciaResponse da qual podemos solicitar a resposta subjacente depois que o pipeline foi sincronizado.
8. Publish/Subscribe
Podemos usar a funcionalidade do intermediário de mensagens Redis para enviar mensagens entre os diferentes componentes do nosso sistema. Verifique se os segmentos de assinante e editor não compartilham a mesma conexão Jedis.
8.1. Assinante
Assine e ouça as mensagens enviadas para um canal:
Jedis jSubscriber = new Jedis();
jSubscriber.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
// handle message
}
}, "channel");
Inscrever-se é um método de bloqueio, você precisará cancelar a inscrição deJedisPubSub explicitamente. Substituímos o métodoonMessage, mas há muito maisuseful methods disponíveis para substituir.
8.2. Editor
Em seguida, basta enviar mensagens para esse mesmo canal a partir da discussão do editor:
Jedis jPublisher = new Jedis();
jPublisher.publish("channel", "test message");
9. Pool de conexão
É importante saber que a maneira como lidamos com nossa instância Jedis é ingênua. Em um cenário do mundo real, você não deseja usar uma única instância em um ambiente com vários threads, pois uma única instância não é segura para threads.
Felizmente, podemos criar facilmente um pool de conexões com o Redis para reutilização sob demanda, um pool que é seguro e confiável, desde que você devolva o recurso ao pool quando terminar.
Vamos criar oJedisPool:
final JedisPoolConfig poolConfig = buildPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig, "localhost");
private JedisPoolConfig buildPoolConfig() {
final JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(128);
poolConfig.setMaxIdle(128);
poolConfig.setMinIdle(16);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(true);
poolConfig.setTestWhileIdle(true);
poolConfig.setMinEvictableIdleTimeMillis(Duration.ofSeconds(60).toMillis());
poolConfig.setTimeBetweenEvictionRunsMillis(Duration.ofSeconds(30).toMillis());
poolConfig.setNumTestsPerEvictionRun(3);
poolConfig.setBlockWhenExhausted(true);
return poolConfig;
}
Como a instância do pool é segura para threads, é possível armazená-la em algum lugar estaticamente, mas você deve destruir o pool para evitar vazamentos quando o aplicativo estiver sendo encerrado.
Agora podemos usar nosso pool de qualquer lugar do aplicativo quando necessário:
try (Jedis jedis = jedisPool.getResource()) {
// do operations with jedis resource
}
Usamos a instrução Java try-with-resources para evitar a necessidade de fechar manualmente o recurso Jedis, mas se você não puder usar esta instrução, também poderá fechar o recurso manualmente na cláusulafinally.
Certifique-se de usar um pool como descrito em seu aplicativo, se você não quiser enfrentar problemas desagradáveis de multithreading. Obviamente, você pode jogar com os parâmetros de configuração do pool para adaptá-lo à melhor configuração do seu sistema.
10. Cluster Redis
Esta implementação do Redis fornece escalabilidade fácil e alta disponibilidade, recomendamos que você leia seuofficial specification se não estiver familiarizado com ele. Não abordaremos a configuração do cluster Redis, pois isso está um pouco fora do escopo deste artigo, mas você não deve ter problemas em fazê-lo quando terminar a documentação.
Quando estivermos prontos, podemos começar a usá-lo em nosso aplicativo:
try (JedisCluster jedisCluster = new JedisCluster(new HostAndPort("localhost", 6379))) {
// use the jedisCluster resource as if it was a normal Jedis resource
} catch (IOException e) {}
Só precisamos fornecer os detalhes do host e da porta de uma de nossas instâncias principais, pois ela descobrirá automaticamente o restante das instâncias no cluster.
Este é certamente um recurso muito poderoso, mas não é uma bala de prata. Ao usar o Redis Cluster, você não pode executar transações nem usar pipelines, dois recursos importantes nos quais muitos aplicativos dependem para garantir a integridade dos dados.
As transações são desativadas porque, em um ambiente em cluster, as chaves serão mantidas em várias instâncias. A atomicidade da operação e a segurança do encadeamento não podem ser garantidas para operações que envolvem a execução de comandos em diferentes instâncias.
Algumas estratégias avançadas de criação de chave garantirão que os dados interessantes para você persistirem na mesma instância sejam persistidos dessa maneira. Em teoria, isso deve permitir que você realize transações com êxito usando uma das instâncias Jedis subjacentes do Redis Cluster.
Infelizmente, atualmente você não pode descobrir em qual instância do Redis uma chave específica é salva usando o Jedis (que na verdade é suportado nativamente pelo Redis); portanto, você não sabe qual das instâncias deve executar a operação da transação. Se você estiver interessado nisso, você pode encontrar mais informaçõeshere.
11. Conclusão
A grande maioria dos recursos da Redis já está disponível no Jedis e seu desenvolvimento avança em um bom ritmo.
Ele permite integrar um poderoso mecanismo de armazenamento em memória em seu aplicativo com pouquíssimos problemas, mas não esqueça de configurar o pool de conexões para evitar problemas de segurança de threads.
Você pode encontrar exemplos de código emGitHub project.